[libcxx-commits] [libcxx] 22e8525 - [libc++][format] Implements range_formatter

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jan 19 08:20:15 PST 2023


Author: Mark de Wever
Date: 2023-01-19T17:20:05+01:00
New Revision: 22e8525dfdd7d78d05ad3873cf94476021e4a08f

URL: https://github.com/llvm/llvm-project/commit/22e8525dfdd7d78d05ad3873cf94476021e4a08f
DIFF: https://github.com/llvm/llvm-project/commit/22e8525dfdd7d78d05ad3873cf94476021e4a08f.diff

LOG: [libc++][format] Implements range_formatter

Implements parts of
- P2286R8 Formatting Ranges
- P2585R0 Improving default container formatting

Depends on D140651

Reviewed By: ldionne, #libc

Differential Revision: https://reviews.llvm.org/D140653

Added: 
    libcxx/include/__format/range_formatter.h
    libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.tests.h
    libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.formatter/format.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.formatter/parse.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp
    libcxx/test/std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp

Modified: 
    libcxx/docs/Status/FormatPaper.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__format/buffer.h
    libcxx/include/__format/format_context.h
    libcxx/include/__format/formatter_output.h
    libcxx/include/__format/parser_std_format_spec.h
    libcxx/include/__format/range_default_formatter.h
    libcxx/include/__memory/allocate_at_least.h
    libcxx/include/format
    libcxx/include/module.modulemap.in
    libcxx/test/libcxx/private_headers.verify.cpp
    libcxx/test/libcxx/transitive_includes/cxx03.csv
    libcxx/test/libcxx/transitive_includes/cxx11.csv
    libcxx/test/libcxx/transitive_includes/cxx14.csv
    libcxx/test/libcxx/transitive_includes/cxx17.csv
    libcxx/test/libcxx/transitive_includes/cxx20.csv
    libcxx/test/libcxx/transitive_includes/cxx2b.csv
    libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
    libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp
    libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h
    libcxx/test/support/format.functions.common.h

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv
index 4a5f1c4b1e2dd..9fb7cd09dc499 100644
--- a/libcxx/docs/Status/FormatPaper.csv
+++ b/libcxx/docs/Status/FormatPaper.csv
@@ -30,7 +30,7 @@ Section,Description,Dependencies,Assignee,Status,First released version
 `P2286R8 <https://wg21.link/P2286R8>`__,"Formatting ranges"
 `[format.syn] <https://wg21.link/format.syn>`_,"Concept ``formattable``",,Mark de Wever,|Complete|, Clang 16
 `[format.string.std] <https://wg21.link/format.string.std>`_,"std-format-spec ``type`` debug",,Mark de Wever,|Complete|,Clang 16
-`[format.range] <https://wg21.link/format.range>`_,"Formatting for ranges: sequences",,Mark de Wever,|In Progress|,
+`[format.range] <https://wg21.link/format.range>`_,"Formatting for ranges: sequences",,Mark de Wever,|Complete|,Clang 16
 `[format.range] <https://wg21.link/format.range>`_,"Formatting for ranges: associative",,Mark de Wever,,
 `[format.range] <https://wg21.link/format.range>`_,"Formatting for ranges: container adaptors",,Mark de Wever,,
 `[format.range] <https://wg21.link/format.range>`_,"Formatting for ranges: ``pair`` and ``tuple``",,Mark de Wever,|Complete|,Clang 16

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index ea52f205d4980..81d488ffc4b20 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -327,6 +327,7 @@ set(files
   __format/formatter_tuple.h
   __format/parser_std_format_spec.h
   __format/range_default_formatter.h
+  __format/range_formatter.h
   __format/unicode.h
   __functional/binary_function.h
   __functional/binary_negate.h

diff  --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h
index 60c1f8093c61d..ddfe76728e9b8 100644
--- a/libcxx/include/__format/buffer.h
+++ b/libcxx/include/__format/buffer.h
@@ -31,6 +31,7 @@
 #include <cstddef>
 #include <string_view>
 #include <type_traits>
+#include <vector>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -493,6 +494,74 @@ struct _LIBCPP_TEMPLATE_VIS __format_to_n_buffer final
     return {_VSTD::move(this->__writer_).__out_it(), this->__size_};
   }
 };
+
+// A dynamically growing buffer intended to be used for retargeting a context.
+//
+// P2286 Formatting ranges adds range formatting support. It allows the user to
+// specify the minimum width for the entire formatted range.  The width of the
+// range is not known until the range is formatted. Formatting is done to an
+// output_iterator so there's no guarantee it would be possible to add the fill
+// to the front of the output. Instead the range is formatted to a temporary
+// buffer and that buffer is formatted as a string.
+//
+// There is an issue with that approach, the format context used in
+// std::formatter<T>::format contains the output iterator used as part of its
+// type. So using this output iterator means there needs to be a new format
+// context and the format arguments need to be retargeted to the new context.
+// This retargeting is done by a basic_format_context specialized for the
+// __iterator of this container.
+template <__fmt_char_type _CharT>
+class _LIBCPP_TEMPLATE_VIS __retarget_buffer {
+public:
+  using value_type = _CharT;
+
+  struct __iterator {
+    using 
diff erence_type = ptr
diff _t;
+
+    _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(__retarget_buffer& __buffer)
+        : __buffer_(std::addressof(__buffer)) {}
+    _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator=(const _CharT& __c) {
+      __buffer_->push_back(__c);
+      return *this;
+    }
+    _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator=(_CharT&& __c) {
+      __buffer_->push_back(__c);
+      return *this;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator*() { return *this; }
+    _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() { return *this; }
+    _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int) { return *this; }
+    __retarget_buffer* __buffer_;
+  };
+
+  _LIBCPP_HIDE_FROM_ABI explicit __retarget_buffer(size_t __size_hint) { __buffer_.reserve(__size_hint); }
+
+  _LIBCPP_HIDE_FROM_ABI __iterator __make_output_iterator() { return __iterator{*this}; }
+
+  _LIBCPP_HIDE_FROM_ABI void push_back(_CharT __c) { __buffer_.push_back(__c); }
+
+  template <__fmt_char_type _InCharT>
+  _LIBCPP_HIDE_FROM_ABI void __copy(basic_string_view<_InCharT> __str) {
+    __buffer_.insert(__buffer_.end(), __str.begin(), __str.end());
+  }
+
+  template <__fmt_char_type _InCharT, class _UnaryOperation>
+  _LIBCPP_HIDE_FROM_ABI void __transform(const _InCharT* __first, const _InCharT* __last, _UnaryOperation __operation) {
+    _LIBCPP_ASSERT(__first <= __last, "not a valid range");
+    std::transform(__first, __last, std::back_inserter(__buffer_), std::move(__operation));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI void __fill(size_t __n, _CharT __value) { __buffer_.insert(__buffer_.end(), __n, __value); }
+
+  _LIBCPP_HIDE_FROM_ABI basic_string_view<_CharT> __view() { return {__buffer_.data(), __buffer_.size()}; }
+
+private:
+  // Use vector instead of string to avoid adding zeros after every append
+  // operation. The buffer is exposed as a string_view and not as a c-string.
+  vector<_CharT> __buffer_;
+};
+
 } // namespace __format
 
 #endif //_LIBCPP_STD_VER > 17

diff  --git a/libcxx/include/__format/format_context.h b/libcxx/include/__format/format_context.h
index 882a6049bb1eb..85e00eb222cf4 100644
--- a/libcxx/include/__format/format_context.h
+++ b/libcxx/include/__format/format_context.h
@@ -11,13 +11,19 @@
 #define _LIBCPP___FORMAT_FORMAT_CONTEXT_H
 
 #include <__availability>
+#include <__concepts/same_as.h>
 #include <__config>
 #include <__format/buffer.h>
+#include <__format/format_arg.h>
+#include <__format/format_arg_store.h>
 #include <__format/format_args.h>
+#include <__format/format_error.h>
 #include <__format/format_fwd.h>
 #include <__iterator/back_insert_iterator.h>
 #include <__iterator/concepts.h>
+#include <__memory/addressof.h>
 #include <__utility/move.h>
+#include <__variant/monostate.h>
 #include <cstddef>
 
 #ifndef _LIBCPP_HAS_NO_LOCALIZATION
@@ -138,8 +144,78 @@ class
       : __out_it_(_VSTD::move(__out_it)), __args_(__args) {}
 #endif
 };
-_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(basic_format_context);
 
+// A specialization for __retarget_buffer
+//
+// See __retarget_buffer for the motivation for this specialization.
+//
+// This context holds a reference to the instance of the basic_format_context
+// that is retargeted. It converts a formatting argument when it is requested
+// during formatting. It is expected that the usage of the arguments is rare so
+// the lookups are not expected to be used often. An alternative would be to
+// convert all elements during construction.
+//
+// The elements of the retargets context are only used when an underlying
+// formatter uses a locale specific formatting or an formatting argument is
+// part for the format spec. For example
+//   format("{:256:{}}", input, 8);
+// Here the width of an element in input is determined dynamically.
+// Note when the top-level element has no width the retargeting is not needed.
+template <class _CharT>
+class _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+    basic_format_context<typename __format::__retarget_buffer<_CharT>::__iterator, _CharT> {
+public:
+  using iterator  = typename __format::__retarget_buffer<_CharT>::__iterator;
+  using char_type = _CharT;
+  template <class _Tp>
+  using formatter_type = formatter<_Tp, _CharT>;
+
+  template <class _Context>
+  _LIBCPP_HIDE_FROM_ABI explicit basic_format_context(iterator __out_it, _Context& __ctx)
+      : __out_it_(std::move(__out_it)),
+#  ifndef _LIBCPP_HAS_NO_LOCALIZATION
+        __loc_([](void* __c) { return static_cast<_Context*>(__c)->locale(); }),
+#  endif
+        __ctx_(std::addressof(__ctx)),
+        __arg_([](void* __c, size_t __id) {
+          return std::visit_format_arg(
+              [&](auto __arg) -> basic_format_arg<basic_format_context> {
+                if constexpr (same_as<decltype(__arg), monostate>)
+                  return {};
+                else if constexpr (same_as<decltype(__arg), typename basic_format_arg<_Context>::handle>)
+                  // At the moment it's not possible for formatting to use a re-targeted handle.
+                  // TODO FMT add this when support is needed.
+                  std::__throw_format_error("Re-targeting handle not supported");
+                else
+                  return basic_format_arg<basic_format_context>{
+                      __format::__determine_arg_t<basic_format_context, decltype(__arg)>(),
+                      __basic_format_arg_value<basic_format_context>(__arg)};
+              },
+              static_cast<_Context*>(__c)->arg(__id));
+        }) {
+  }
+
+  _LIBCPP_HIDE_FROM_ABI basic_format_arg<basic_format_context> arg(size_t __id) const noexcept {
+    return __arg_(__ctx_, __id);
+  }
+#  ifndef _LIBCPP_HAS_NO_LOCALIZATION
+  _LIBCPP_HIDE_FROM_ABI _VSTD::locale locale() { return __loc_(__ctx_); }
+#  endif
+  _LIBCPP_HIDE_FROM_ABI iterator out() { return std::move(__out_it_); }
+  _LIBCPP_HIDE_FROM_ABI void advance_to(iterator __it) { __out_it_ = std::move(__it); }
+
+private:
+  iterator __out_it_;
+
+#  ifndef _LIBCPP_HAS_NO_LOCALIZATION
+  std::locale (*__loc_)(void* __ctx);
+#  endif
+
+  void* __ctx_;
+  basic_format_arg<basic_format_context> (*__arg_)(void* __ctx, size_t __id);
+};
+
+_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(basic_format_context);
 #endif //_LIBCPP_STD_VER > 17
 
 _LIBCPP_END_NAMESPACE_STD

diff  --git a/libcxx/include/__format/formatter_output.h b/libcxx/include/__format/formatter_output.h
index 70eae1564cbcc..467692559ce91 100644
--- a/libcxx/include/__format/formatter_output.h
+++ b/libcxx/include/__format/formatter_output.h
@@ -102,6 +102,10 @@ _LIBCPP_HIDE_FROM_ABI auto __copy(basic_string_view<_CharT> __str, output_iterat
   if constexpr (_VSTD::same_as<decltype(__out_it), _VSTD::back_insert_iterator<__format::__output_buffer<_OutCharT>>>) {
     __out_it.__get_container()->__copy(__str);
     return __out_it;
+  } else if constexpr (_VSTD::same_as<decltype(__out_it),
+                                      typename __format::__retarget_buffer<_OutCharT>::__iterator>) {
+    __out_it.__buffer_->__copy(__str);
+    return __out_it;
   } else {
     return std::ranges::copy(__str, _VSTD::move(__out_it)).out;
   }
@@ -132,6 +136,10 @@ __transform(const _CharT* __first,
   if constexpr (_VSTD::same_as<decltype(__out_it), _VSTD::back_insert_iterator<__format::__output_buffer<_OutCharT>>>) {
     __out_it.__get_container()->__transform(__first, __last, _VSTD::move(__operation));
     return __out_it;
+  } else if constexpr (_VSTD::same_as<decltype(__out_it),
+                                      typename __format::__retarget_buffer<_OutCharT>::__iterator>) {
+    __out_it.__buffer_->__transform(__first, __last, _VSTD::move(__operation));
+    return __out_it;
   } else {
     return std::ranges::transform(__first, __last, _VSTD::move(__out_it), __operation).out;
   }
@@ -145,6 +153,9 @@ _LIBCPP_HIDE_FROM_ABI _OutIt __fill(_OutIt __out_it, size_t __n, _CharT __value)
   if constexpr (_VSTD::same_as<decltype(__out_it), _VSTD::back_insert_iterator<__format::__output_buffer<_CharT>>>) {
     __out_it.__get_container()->__fill(__n, __value);
     return __out_it;
+  } else if constexpr (_VSTD::same_as<decltype(__out_it), typename __format::__retarget_buffer<_CharT>::__iterator>) {
+    __out_it.__buffer_->__fill(__n, __value);
+    return __out_it;
   } else {
     return std::ranges::fill_n(_VSTD::move(__out_it), __n, __value);
   }

diff  --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h
index 36f6505e7debf..c03cec9796364 100644
--- a/libcxx/include/__format/parser_std_format_spec.h
+++ b/libcxx/include/__format/parser_std_format_spec.h
@@ -139,6 +139,7 @@ inline constexpr __fields __fields_pointer{.__type_ = true};
 
 #  if _LIBCPP_STD_VER > 20
 inline constexpr __fields __fields_tuple{.__type_ = false, .__allow_colon_in_fill_ = true};
+inline constexpr __fields __fields_range{.__type_ = false, .__allow_colon_in_fill_ = true};
 #  endif
 
 enum class _LIBCPP_ENUM_VIS __alignment : uint8_t {

diff  --git a/libcxx/include/__format/range_default_formatter.h b/libcxx/include/__format/range_default_formatter.h
index 56558f3ffb6c4..ee1dc52e37526 100644
--- a/libcxx/include/__format/range_default_formatter.h
+++ b/libcxx/include/__format/range_default_formatter.h
@@ -19,9 +19,11 @@
 #include <__config>
 #include <__format/concepts.h>
 #include <__format/formatter.h>
+#include <__format/range_formatter.h>
 #include <__ranges/concepts.h>
 #include <__type_traits/remove_cvref.h>
 #include <__utility/pair.h>
+#include <string_view>
 #include <tuple>
 
 _LIBCPP_BEGIN_NAMESPACE_STD
@@ -104,7 +106,28 @@ struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __range_default_formatte
 
 template <ranges::input_range _Rp, class _CharT>
 struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __range_default_formatter<range_format::sequence, _Rp, _CharT> {
-  __range_default_formatter() = delete; // TODO FMT Implement
+private:
+  using __maybe_const_r = __fmt_maybe_const<_Rp, _CharT>;
+  range_formatter<remove_cvref_t<ranges::range_reference_t<__maybe_const_r>>, _CharT> __underlying_;
+
+public:
+  _LIBCPP_HIDE_FROM_ABI constexpr void set_separator(basic_string_view<_CharT> __separator) {
+    __underlying_.set_separator(__separator);
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr void
+  set_brackets(basic_string_view<_CharT> __opening_bracket, basic_string_view<_CharT> __closing_bracket) {
+    __underlying_.set_brackets(__opening_bracket, __closing_bracket);
+  }
+
+  template <class _ParseContext>
+  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) {
+    return __underlying_.parse(__ctx);
+  }
+
+  template <class FormatContext>
+  _LIBCPP_HIDE_FROM_ABI typename FormatContext::iterator format(__maybe_const_r& __range, FormatContext& __ctx) const {
+    return __underlying_.format(__range, __ctx);
+  }
 };
 
 template <ranges::input_range _Rp, class _CharT>

diff  --git a/libcxx/include/__format/range_formatter.h b/libcxx/include/__format/range_formatter.h
new file mode 100644
index 0000000000000..f2a4342e2ec66
--- /dev/null
+++ b/libcxx/include/__format/range_formatter.h
@@ -0,0 +1,245 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___FORMAT_RANGE_FORMATTER_H
+#define _LIBCPP___FORMAT_RANGE_FORMATTER_H
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#include <__algorithm/ranges_copy.h>
+#include <__availability>
+#include <__chrono/statically_widen.h>
+#include <__concepts/same_as.h>
+#include <__config>
+#include <__format/buffer.h>
+#include <__format/concepts.h>
+#include <__format/format_args.h>
+#include <__format/format_context.h>
+#include <__format/format_error.h>
+#include <__format/formatter.h>
+#include <__format/formatter_output.h>
+#include <__format/parser_std_format_spec.h>
+#include <__iterator/back_insert_iterator.h>
+#include <__ranges/concepts.h>
+#include <__type_traits/remove_cvref.h>
+#include <string_view>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 20
+
+template <class _Tp, class _CharT = char>
+  requires same_as<remove_cvref_t<_Tp>, _Tp> && formattable<_Tp, _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT range_formatter {
+  _LIBCPP_HIDE_FROM_ABI constexpr void set_separator(basic_string_view<_CharT> __separator) {
+    __separator_ = __separator;
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr void
+  set_brackets(basic_string_view<_CharT> __opening_bracket, basic_string_view<_CharT> __closing_bracket) {
+    __opening_bracket_ = __opening_bracket;
+    __closing_bracket_ = __closing_bracket;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr formatter<_Tp, _CharT>& underlying() { return __underlying_; }
+  _LIBCPP_HIDE_FROM_ABI constexpr const formatter<_Tp, _CharT>& underlying() const { return __underlying_; }
+
+  template <class _ParseContext>
+  _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __parse_ctx) {
+    const _CharT* __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_range);
+    const _CharT* __end   = __parse_ctx.end();
+    if (__begin == __end)
+      return __begin;
+
+    // The n field overrides a possible m type, therefore delay applying the
+    // effect of n until the type has been procesed.
+    bool __clear_brackets = (*__begin == _CharT('n'));
+    if (__clear_brackets) {
+      ++__begin;
+      if (__begin == __end) {
+        // Since there is no more data, clear the brackets before returning.
+        set_brackets({}, {});
+        return __begin;
+      }
+    }
+
+    __parse_type(__begin, __end);
+    if (__clear_brackets)
+      set_brackets({}, {});
+    if (__begin == __end)
+      return __begin;
+
+    bool __has_range_underlying_spec = *__begin == _CharT(':');
+    if (__parser_.__type_ != __format_spec::__type::__default) {
+      // [format.range.formatter]/6
+      //   If the range-type is s or ?s, then there shall be no n option and no
+      //   range-underlying-spec.
+      if (__clear_brackets) {
+        if (__parser_.__type_ == __format_spec::__type::__string)
+          std::__throw_format_error("The n option and type s can't be used together");
+        std::__throw_format_error("The n option and type ?s can't be used together");
+      }
+      if (__has_range_underlying_spec) {
+        if (__parser_.__type_ == __format_spec::__type::__string)
+          std::__throw_format_error("Type s and an underlying format specification can't be used together");
+        std::__throw_format_error("Type ?s and an underlying format specification can't be used together");
+      }
+    } else if (!__has_range_underlying_spec)
+      std::__set_debug_format(__underlying_);
+
+    if (__has_range_underlying_spec) {
+      // range-underlying-spec:
+      //   :  format-spec
+      ++__begin;
+      if (__begin == __end)
+        return __begin;
+
+      __parse_ctx.advance_to(__begin);
+      __begin = __underlying_.parse(__parse_ctx);
+    }
+
+    if (__begin != __end && *__begin != _CharT('}'))
+      std::__throw_format_error("The format-spec should consume the input or end with a '}'");
+
+    return __begin;
+  }
+
+  template <ranges::input_range _Rp, class _FormatContext>
+    requires formattable<ranges::range_reference_t<_Rp>, _CharT> &&
+             same_as<remove_cvref_t<ranges::range_reference_t<_Rp>>, _Tp>
+  _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(_Rp&& __range, _FormatContext& __ctx) const {
+    __format_spec::__parsed_specifications<_CharT> __specs = __parser_.__get_parsed_std_specifications(__ctx);
+
+    if (!__specs.__has_width())
+      return __format_range(__range, __ctx, __specs);
+
+    // The size of the buffer needed is:
+    // - open bracket characters
+    // - close bracket character
+    // - n elements where every element may have a 
diff erent size
+    // - (n -1) separators
+    // The size of the element is hard to predict, knowing the type helps but
+    // it depends on the format-spec. As an initial estimate we guess 6
+    // characters.
+    // Typically both brackets are 1 character and the separator is 2
+    // characters. Which means there will be
+    //   (n - 1) * 2 + 1 + 1 = n * 2 character
+    // So estimate 8 times the range size as buffer.
+    __format::__retarget_buffer<_CharT> __buffer{8 * ranges::size(__range)};
+    basic_format_context<typename __format::__retarget_buffer<_CharT>::__iterator, _CharT> __c{
+        __buffer.__make_output_iterator(), __ctx};
+
+    __format_range(__range, __c, __specs);
+
+    return __formatter::__write_string_no_precision(__buffer.__view(), __ctx.out(), __specs);
+  }
+
+  template <ranges::input_range _Rp, class _FormatContext>
+  typename _FormatContext::iterator _LIBCPP_HIDE_FROM_ABI
+  __format_range(_Rp&& __range, _FormatContext& __ctx, __format_spec::__parsed_specifications<_CharT> __specs) const {
+    if constexpr (same_as<_Tp, _CharT>) {
+      switch (__specs.__std_.__type_) {
+      case __format_spec::__type::__string:
+      case __format_spec::__type::__debug:
+        return __format_as_string(__range, __ctx, __specs.__std_.__type_ == __format_spec::__type::__debug);
+      default:
+        return __format_as_sequence(__range, __ctx);
+      }
+    } else
+      return __format_as_sequence(__range, __ctx);
+  }
+
+  template <ranges::input_range _Rp, class _FormatContext>
+  _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator
+  __format_as_string(_Rp&& __range, _FormatContext& __ctx, bool __debug_format) const {
+    // When the range is contiguous use a basic_string_view instead to avoid a
+    // copy of the underlying data. The basic_string_view formatter
+    // specialization is the "basic" string formatter in libc++.
+    if constexpr (ranges::contiguous_range<_Rp>) {
+      std::formatter<basic_string_view<_CharT>, _CharT> __formatter;
+      if (__debug_format)
+        __formatter.set_debug_format();
+      return __formatter.format(basic_string_view<_CharT>{__range.data(), __range.size()}, __ctx);
+    } else {
+      std::formatter<basic_string<_CharT>, _CharT> __formatter;
+      if (__debug_format)
+        __formatter.set_debug_format();
+      // P2106's from_range has not been implemented yet. Instead use a simple
+      // copy operation.
+      // TODO FMT use basic_string's "from_range" constructor.
+      // return std::formatter<basic_string<_CharT>, _CharT>{}.format(basic_string<_CharT>{from_range, __range}, __ctx);
+      basic_string<_CharT> __str;
+      ranges::copy(__range, back_insert_iterator{__str});
+      return __formatter.format(__str, __ctx);
+    }
+  }
+
+  template <ranges::input_range _Rp, class _FormatContext>
+  _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator
+  __format_as_sequence(_Rp&& __range, _FormatContext& __ctx) const {
+    __ctx.advance_to(ranges::copy(__opening_bracket_, __ctx.out()).out);
+    bool __use_separator = false;
+    for (auto&& __e : __range) {
+      if (__use_separator)
+        __ctx.advance_to(ranges::copy(__separator_, __ctx.out()).out);
+      else
+        __use_separator = true;
+
+      __ctx.advance_to(__underlying_.format(__e, __ctx));
+    }
+
+    return ranges::copy(__closing_bracket_, __ctx.out()).out;
+  }
+
+  __format_spec::__parser<_CharT> __parser_{.__alignment_ = __format_spec::__alignment::__left};
+
+private:
+  _LIBCPP_HIDE_FROM_ABI constexpr void __parse_type(const _CharT*& __begin, const _CharT* __end) {
+    switch (*__begin) {
+    case _CharT('m'):
+      if constexpr (__fmt_pair_like<_Tp>) {
+        set_brackets(_LIBCPP_STATICALLY_WIDEN(_CharT, "{"), _LIBCPP_STATICALLY_WIDEN(_CharT, "}"));
+        set_separator(_LIBCPP_STATICALLY_WIDEN(_CharT, ", "));
+        ++__begin;
+      } else
+        std::__throw_format_error("The range-format-spec type m requires two elements for a pair or tuple");
+      break;
+
+    case _CharT('s'):
+      if constexpr (same_as<_Tp, _CharT>) {
+        __parser_.__type_ = __format_spec::__type::__string;
+        ++__begin;
+      } else
+        std::__throw_format_error("The range-format-spec type s requires formatting a character type");
+      break;
+
+    case _CharT('?'):
+      ++__begin;
+      if (__begin == __end || *__begin != _CharT('s'))
+        std::__throw_format_error("The format-spec should consume the input or end with a '}'");
+      if constexpr (same_as<_Tp, _CharT>) {
+        __parser_.__type_ = __format_spec::__type::__debug;
+        ++__begin;
+      } else
+        std::__throw_format_error("The range-format-spec type ?s requires formatting a character type");
+    }
+  }
+
+  formatter<_Tp, _CharT> __underlying_;
+  basic_string_view<_CharT> __separator_       = _LIBCPP_STATICALLY_WIDEN(_CharT, ", ");
+  basic_string_view<_CharT> __opening_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, "[");
+  basic_string_view<_CharT> __closing_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, "]");
+};
+
+#endif //_LIBCPP_STD_VER > 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___FORMAT_RANGE_FORMATTER_H

diff  --git a/libcxx/include/__memory/allocate_at_least.h b/libcxx/include/__memory/allocate_at_least.h
index 7ce588a25d1be..ef205f855cb2a 100644
--- a/libcxx/include/__memory/allocate_at_least.h
+++ b/libcxx/include/__memory/allocate_at_least.h
@@ -25,6 +25,7 @@ struct allocation_result {
   _Pointer ptr;
   size_t count;
 };
+_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(allocation_result);
 
 template <class _Alloc>
 [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr

diff  --git a/libcxx/include/format b/libcxx/include/format
index 900f27c9e03e5..aaac95b39c4c9 100644
--- a/libcxx/include/format
+++ b/libcxx/include/format
@@ -130,6 +130,11 @@ namespace std {
       requires same_as<R, remove_cvref_t<R>>
     constexpr range_format format_kind<R> = see below;          // since C++23
 
+  // [format.range.formatter], class template range_formatter
+  template<class T, class charT = char>
+    requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+  class range_formatter;                                        // since C++23
+
   // [format.range.fmtdef], class template range-default-formatter
   template<range_format K, ranges::input_range R, class charT>
     struct range-default-formatter;                             // exposition only, since C++23
@@ -194,6 +199,7 @@ namespace std {
 #include <__format/formatter_tuple.h>
 #include <__format/parser_std_format_spec.h>
 #include <__format/range_default_formatter.h>
+#include <__format/range_formatter.h>
 #include <__format/unicode.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 5d4cf53aa334e..81f503e29c6a0 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -871,6 +871,7 @@ module std [system] {
       module formatter_tuple                 { private header "__format/formatter_tuple.h" }
       module parser_std_format_spec          { private header "__format/parser_std_format_spec.h" }
       module range_default_formatter         { private header "__format/range_default_formatter.h" }
+      module range_formatter                 { private header "__format/range_formatter.h" }
       module unicode                         { private header "__format/unicode.h" }
     }
   }

diff  --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp
index f88ee37062d46..27b1c90ff5bfa 100644
--- a/libcxx/test/libcxx/private_headers.verify.cpp
+++ b/libcxx/test/libcxx/private_headers.verify.cpp
@@ -359,6 +359,7 @@ END-SCRIPT
 #include <__format/formatter_tuple.h> // expected-error@*:* {{use of private header from outside its module: '__format/formatter_tuple.h'}}
 #include <__format/parser_std_format_spec.h> // expected-error@*:* {{use of private header from outside its module: '__format/parser_std_format_spec.h'}}
 #include <__format/range_default_formatter.h> // expected-error@*:* {{use of private header from outside its module: '__format/range_default_formatter.h'}}
+#include <__format/range_formatter.h> // expected-error@*:* {{use of private header from outside its module: '__format/range_formatter.h'}}
 #include <__format/unicode.h> // expected-error@*:* {{use of private header from outside its module: '__format/unicode.h'}}
 #include <__functional/binary_function.h> // expected-error@*:* {{use of private header from outside its module: '__functional/binary_function.h'}}
 #include <__functional/binary_negate.h> // expected-error@*:* {{use of private header from outside its module: '__functional/binary_negate.h'}}

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index 08f449b9e2425..c245d11b5a4e0 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -359,6 +359,7 @@ format string
 format string_view
 format tuple
 format type_traits
+format vector
 format version
 forward_list algorithm
 forward_list atomic

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index 6ecca62114a46..74bf87284072b 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -359,6 +359,7 @@ format string
 format string_view
 format tuple
 format type_traits
+format vector
 format version
 forward_list algorithm
 forward_list atomic

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index fb50933e5233a..7ee16527026eb 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -361,6 +361,7 @@ format string
 format string_view
 format tuple
 format type_traits
+format vector
 format version
 forward_list algorithm
 forward_list atomic

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index fb50933e5233a..7ee16527026eb 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -361,6 +361,7 @@ format string
 format string_view
 format tuple
 format type_traits
+format vector
 format version
 forward_list algorithm
 forward_list atomic

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 7e7661d30958f..f48e46896b7ea 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -125,6 +125,7 @@ chrono string
 chrono string_view
 chrono tuple
 chrono type_traits
+chrono vector
 chrono version
 cinttypes cstdint
 cmath type_traits
@@ -369,6 +370,7 @@ format string
 format string_view
 format tuple
 format type_traits
+format vector
 format version
 forward_list algorithm
 forward_list atomic

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx2b.csv b/libcxx/test/libcxx/transitive_includes/cxx2b.csv
index 21b32bf401a29..7401de433dbca 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx2b.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx2b.csv
@@ -95,6 +95,7 @@ chrono string
 chrono string_view
 chrono tuple
 chrono type_traits
+chrono vector
 chrono version
 cinttypes cstdint
 cmath version
@@ -277,6 +278,7 @@ format string
 format string_view
 format tuple
 format type_traits
+format vector
 format version
 forward_list compare
 forward_list cstddef

diff  --git a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
index 4e9e526bf032e..e17a6d0da2d47 100644
--- a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp
@@ -200,11 +200,11 @@ void test_P1636() {
 // TODO validate whether the test is correct after the paper has been accepted.
 template <class CharT>
 void test_P2286() {
-  assert_is_not_formattable<std::array<int, 42>, CharT>();
-  assert_is_not_formattable<std::vector<int>, CharT>();
-  assert_is_not_formattable<std::deque<int>, CharT>();
-  assert_is_not_formattable<std::forward_list<int>, CharT>();
-  assert_is_not_formattable<std::list<int>, CharT>();
+  assert_is_formattable<std::array<int, 42>, CharT>();
+  assert_is_formattable<std::vector<int>, CharT>();
+  assert_is_formattable<std::deque<int>, CharT>();
+  assert_is_formattable<std::forward_list<int>, CharT>();
+  assert_is_formattable<std::list<int>, CharT>();
 
   assert_is_not_formattable<std::set<int>, CharT>();
   assert_is_not_formattable<std::map<int, int>, CharT>();
@@ -220,9 +220,9 @@ void test_P2286() {
   assert_is_not_formattable<std::queue<int>, CharT>();
   assert_is_not_formattable<std::priority_queue<int>, CharT>();
 
-  assert_is_not_formattable<std::span<int>, CharT>();
+  assert_is_formattable<std::span<int>, CharT>();
 
-  assert_is_not_formattable<std::valarray<int>, CharT>();
+  assert_is_formattable<std::valarray<int>, CharT>();
 
   assert_is_formattable<std::pair<int, int>, CharT>();
   assert_is_formattable<std::tuple<int>, CharT>();

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp
new file mode 100644
index 0000000000000..78e04fe367f48
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <format>
+
+//  template<class T, class charT = char>
+//    requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+//  class range_formatter;
+
+// template<class... Args>
+//   string format(format_string<Args...> fmt, Args&&... args);
+// template<class... Args>
+//   wstring format(wformat_string<Args...> fmt, Args&&... args);
+
+#include <format>
+#include <cassert>
+
+#include "format.functions.tests.h"
+#include "test_format_string.h"
+#include "test_macros.h"
+#include "assert_macros.h"
+
+auto test = []<class CharT, class... Args>(
+                std::basic_string_view<CharT> expected, test_format_string<CharT, Args...> fmt, Args&&... args) {
+  std::basic_string<CharT> out = std::format(fmt, std::forward<Args>(args)...);
+  TEST_REQUIRE(
+      out == expected,
+      test_concat_message("\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception = []<class CharT, class... Args>(std::string_view, std::basic_string_view<CharT>, Args&&...) {
+  // After P2216 most exceptions thrown by std::format become ill-formed.
+  // Therefore this tests does nothing.
+};
+
+int main(int, char**) {
+  format_tests<char>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests<wchar_t>(test, test_exception);
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.tests.h b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.tests.h
new file mode 100644
index 0000000000000..b2627d596fe2f
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.tests.h
@@ -0,0 +1,1287 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FORMATTER_FORMAT_FUNCTIONS_TESTS_H
+#define TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FORMATTER_FORMAT_FUNCTIONS_TESTS_H
+
+#include <algorithm>
+#include <array>
+#include <charconv>
+#include <concepts>
+#include <format>
+#include <list>
+#include <ranges>
+#include <span>
+#include <vector>
+
+#include "format.functions.common.h"
+#include "make_string.h"
+#include "platform_support.h" // locale name macros
+#include "test_iterators.h"
+#include "test_macros.h"
+
+//
+// Char
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_char_default(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  // Note when no range-underlying-spec is present the char is escaped,
+  check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{}"), input);
+
+  // when one is present there is no escaping,
+  check(SV("[H, e, l, l, o]"), SV("{::}"), input);
+  // unless forced by the type specifier.
+  check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{::?}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("['H', 'e', 'l', 'l', 'o']     "), SV("{:30}"), input);
+  check(SV("['H', 'e', 'l', 'l', 'o']*****"), SV("{:*<30}"), input);
+  check(SV("__['H', 'e', 'l', 'l', 'o']___"), SV("{:_^30}"), input);
+  check(SV("#####['H', 'e', 'l', 'l', 'o']"), SV("{:#>30}"), input);
+
+  check(SV("['H', 'e', 'l', 'l', 'o']     "), SV("{:{}}"), input, 30);
+  check(SV("['H', 'e', 'l', 'l', 'o']*****"), SV("{:*<{}}"), input, 30);
+  check(SV("__['H', 'e', 'l', 'l', 'o']___"), SV("{:_^{}}"), input, 30);
+  check(SV("#####['H', 'e', 'l', 'l', 'o']"), SV("{:#>{}}"), input, 30);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__'H', 'e', 'l', 'l', 'o'___"), SV("{:_^28n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[H   , e   , l   , l   , o   ]"), SV("{::4}"), input);
+  check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<4}"), input);
+  check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^4}"), input);
+  check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>4}"), input);
+
+  check(SV("[H   , e   , l   , l   , o   ]"), SV("{::{}}"), input, 4);
+  check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<{}}"), input, 4);
+  check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^{}}"), input, 4);
+  check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>{}}"), input, 4);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("A sign field isn't allowed in this format-spec", SV("{::-}"), input);
+  check_exception("A sign field isn't allowed in this format-spec", SV("{::+}"), input);
+  check_exception("A sign field isn't allowed in this format-spec", SV("{:: }"), input);
+
+  check(SV("[72, 101, 108, 108, 111]"), SV("{::-d}"), input);
+  check(SV("[+72, +101, +108, +108, +111]"), SV("{::+d}"), input);
+  check(SV("[ 72,  101,  108,  108,  111]"), SV("{:: d}"), input);
+
+  // *** alternate form ***
+  check_exception("An alternate form field isn't allowed in this format-spec", SV("{::#}"), input);
+
+  check(SV("[0x48, 0x65, 0x6c, 0x6c, 0x6f]"), SV("{::#x}"), input);
+
+  // *** zero-padding ***
+  check_exception("A zero-padding field isn't allowed in this format-spec", SV("{::05}"), input);
+
+  check(SV("[00110, 00145, 00154, 00154, 00157]"), SV("{::05o}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check(SV("[H, e, l, l, o]"), SV("{::L}"), input);
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("bBcdoxX?"))
+    check_exception("The format-spec type has a type not supported for a char argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^25::>2}"), input);
+  check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>2}"), input, 25);
+  check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>{}}"), input, 25, 2);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>2}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 25);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_char_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV("Hello"), SV("{:s}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("Hello   "), SV("{:8s}"), input);
+  check(SV("Hello***"), SV("{:*<8s}"), input);
+  check(SV("_Hello__"), SV("{:_^8s}"), input);
+  check(SV("###Hello"), SV("{:#>8s}"), input);
+
+  check(SV("Hello   "), SV("{:{}s}"), input, 8);
+  check(SV("Hello***"), SV("{:*<{}s}"), input, 8);
+  check(SV("_Hello__"), SV("{:_^{}s}"), input, 8);
+  check(SV("###Hello"), SV("{:#>{}s}"), input, 8);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<s}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<s}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<s}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-s}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+s}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: s}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#s}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0s}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.s}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:Ls}"), input);
+
+  // *** n
+  check_exception("The n option and type s can't be used together", SV("{:ns}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+
+  // ***** Only underlying has a format-spec
+  check_exception("Type s and an underlying format specification can't be used together", SV("{:s:}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("bBcdoxX?"))
+    check_exception("The format-spec type has a type not supported for a char argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check_exception("Type s and an underlying format specification can't be used together", SV("{:5s:5}"), input);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_char_escaped_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV(R"("\"Hello'")"), SV("{:?s}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV(R"("\"Hello'"   )"), SV("{:13?s}"), input);
+  check(SV(R"("\"Hello'"***)"), SV("{:*<13?s}"), input);
+  check(SV(R"(_"\"Hello'"__)"), SV("{:_^13?s}"), input);
+  check(SV(R"(###"\"Hello'")"), SV("{:#>13?s}"), input);
+
+  check(SV(R"("\"Hello'"   )"), SV("{:{}?s}"), input, 13);
+  check(SV(R"("\"Hello'"***)"), SV("{:*<{}?s}"), input, 13);
+  check(SV(R"(_"\"Hello'"__)"), SV("{:_^{}?s}"), input, 13);
+  check(SV(R"(###"\"Hello'")"), SV("{:#>{}?s}"), input, 13);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<?s}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<?s}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<?s}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-?s}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+?s}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: ?s}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#?s}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0?s}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.?s}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L?s}"), input);
+
+  // *** n
+  check_exception("The n option and type ?s can't be used together", SV("{:n?s}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+
+  // ***** Only underlying has a format-spec
+  check_exception("Type ?s and an underlying format specification can't be used together", SV("{:?s:}"), input);
+
+  // ***** Both have a format-spec
+  check_exception("Type ?s and an underlying format specification can't be used together", SV("{:5?s:5}"), input);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_char(TestFunction check, ExceptionTest check_exception) {
+  test_char_default<CharT>(
+      check, check_exception, std::array{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')});
+
+  // This tests two 
diff erent implementations in libc++. A basic_string_view
+  // formatter if the range is contiguous, a basic_string otherwise.
+  test_char_escaped_string<CharT>(
+      check,
+      check_exception,
+      std::array{CharT('"'), CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o'), CharT('\'')});
+  test_char_escaped_string<CharT>(
+      check,
+      check_exception,
+      std::list{CharT('"'), CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o'), CharT('\'')});
+
+  // This tests two 
diff erent implementations in libc++. A basic_string_view
+  // formatter if the range is contiguous, a basic_string otherwise.
+  test_char_string<CharT>(
+      check, check_exception, std::array{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')});
+  test_char_string<CharT>(
+      check, check_exception, std::list{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')});
+}
+
+//
+// char -> wchar_t
+//
+
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+template <class TestFunction, class ExceptionTest>
+void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) {
+  test_char_default<wchar_t>(check, check_exception, std::array{'H', 'e', 'l', 'l', 'o'});
+
+  // The types s and ?s may only be used when using range_formatter<T, charT>
+  // where the types T and charT are the same. This means this can't be used for
+  // range_formatter<wchar_t, char> even when formatter<wchar_t, char> has a
+  // debug-enabled specialization.
+
+  using CharT = wchar_t;
+  check_exception("The range-format-spec type s requires formatting a character type",
+                  SV("{:s}"),
+                  std::array{'H', 'e', 'l', 'l', 'o'});
+  check_exception("The range-format-spec type ?s requires formatting a character type",
+                  SV("{:?s}"),
+                  std::array{'H', 'e', 'l', 'l', 'o'});
+}
+#endif
+
+//
+// Bool
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_bool(TestFunction check, ExceptionTest check_exception) {
+  std::array input{true, true, false};
+
+  check(SV("[true, true, false]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[true, true, false]     "), SV("{:24}"), input);
+  check(SV("[true, true, false]*****"), SV("{:*<24}"), input);
+  check(SV("__[true, true, false]___"), SV("{:_^24}"), input);
+  check(SV("#####[true, true, false]"), SV("{:#>24}"), input);
+
+  check(SV("[true, true, false]     "), SV("{:{}}"), input, 24);
+  check(SV("[true, true, false]*****"), SV("{:*<{}}"), input, 24);
+  check(SV("__[true, true, false]___"), SV("{:_^{}}"), input, 24);
+  check(SV("#####[true, true, false]"), SV("{:#>{}}"), input, 24);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__true, true, false___"), SV("{:_^22n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[true   , true   , false  ]"), SV("{::7}"), input);
+  check(SV("[true***, true***, false**]"), SV("{::*<7}"), input);
+  check(SV("[_true__, _true__, _false_]"), SV("{::_^7}"), input);
+  check(SV("[:::true, :::true, ::false]"), SV("{:::>7}"), input);
+
+  check(SV("[true   , true   , false  ]"), SV("{::{}}"), input, 7);
+  check(SV("[true***, true***, false**]"), SV("{::*<{}}"), input, 7);
+  check(SV("[_true__, _true__, _false_]"), SV("{::_^{}}"), input, 7);
+  check(SV("[:::true, :::true, ::false]"), SV("{:::>{}}"), input, 7);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("A sign field isn't allowed in this format-spec", SV("{::-}"), input);
+  check_exception("A sign field isn't allowed in this format-spec", SV("{::+}"), input);
+  check_exception("A sign field isn't allowed in this format-spec", SV("{:: }"), input);
+
+  check(SV("[1, 1, 0]"), SV("{::-d}"), input);
+  check(SV("[+1, +1, +0]"), SV("{::+d}"), input);
+  check(SV("[ 1,  1,  0]"), SV("{:: d}"), input);
+
+  // *** alternate form ***
+  check_exception("An alternate form field isn't allowed in this format-spec", SV("{::#}"), input);
+
+  check(SV("[0x1, 0x1, 0x0]"), SV("{::#x}"), input);
+
+  // *** zero-padding ***
+  check_exception("A zero-padding field isn't allowed in this format-spec", SV("{::05}"), input);
+
+  check(SV("[00001, 00001, 00000]"), SV("{::05o}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check(SV("[true, true, false]"), SV("{::L}"), input);
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("bBdosxX"))
+    check_exception("The format-spec type has a type not supported for a bool argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^32::>7}"), input);
+  check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>7}"), input, 32);
+  check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>{}}"), input, 32, 7);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>5}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 32);
+}
+
+//
+// Integral
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_int(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV("[1, 2, 42, -42]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[1, 2, 42, -42]     "), SV("{:20}"), input);
+  check(SV("[1, 2, 42, -42]*****"), SV("{:*<20}"), input);
+  check(SV("__[1, 2, 42, -42]___"), SV("{:_^20}"), input);
+  check(SV("#####[1, 2, 42, -42]"), SV("{:#>20}"), input);
+
+  check(SV("[1, 2, 42, -42]     "), SV("{:{}}"), input, 20);
+  check(SV("[1, 2, 42, -42]*****"), SV("{:*<{}}"), input, 20);
+  check(SV("__[1, 2, 42, -42]___"), SV("{:_^{}}"), input, 20);
+  check(SV("#####[1, 2, 42, -42]"), SV("{:#>{}}"), input, 20);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__1, 2, 42, -42___"), SV("{:_^18n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[    1,     2,    42,   -42]"), SV("{::5}"), input);
+  check(SV("[1****, 2****, 42***, -42**]"), SV("{::*<5}"), input);
+  check(SV("[__1__, __2__, _42__, _-42_]"), SV("{::_^5}"), input);
+  check(SV("[::::1, ::::2, :::42, ::-42]"), SV("{:::>5}"), input);
+
+  check(SV("[    1,     2,    42,   -42]"), SV("{::{}}"), input, 5);
+  check(SV("[1****, 2****, 42***, -42**]"), SV("{::*<{}}"), input, 5);
+  check(SV("[__1__, __2__, _42__, _-42_]"), SV("{::_^{}}"), input, 5);
+  check(SV("[::::1, ::::2, :::42, ::-42]"), SV("{:::>{}}"), input, 5);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check(SV("[1, 2, 42, -42]"), SV("{::-}"), input);
+  check(SV("[+1, +2, +42, -42]"), SV("{::+}"), input);
+  check(SV("[ 1,  2,  42, -42]"), SV("{:: }"), input);
+
+  // *** alternate form ***
+  check(SV("[0x1, 0x2, 0x2a, -0x2a]"), SV("{::#x}"), input);
+
+  // *** zero-padding ***
+  check(SV("[00001, 00002, 00042, -0042]"), SV("{::05}"), input);
+  check(SV("[00001, 00002, 0002a, -002a]"), SV("{::05x}"), input);
+  check(SV("[0x001, 0x002, 0x02a, -0x2a]"), SV("{::#05x}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check(SV("[1, 2, 42, -42]"), SV("{::L}"), input); // does nothing in this test, but is accepted.
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("bBcdoxX"))
+    check_exception("The format-spec type has a type not supported for an integer argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^33::>5}"), input);
+  check(SV("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^{}::>5}"), input, 33);
+  check(SV("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^{}::>{}}"), input, 33, 5);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>5}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 33);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_int(TestFunction check, ExceptionTest check_exception) {
+  test_int<CharT>(check, check_exception, std::array{1, 2, 42, -42});
+  test_int<CharT>(check, check_exception, std::list{1, 2, 42, -42});
+  test_int<CharT>(check, check_exception, std::vector{1, 2, 42, -42});
+  std::array input{1, 2, 42, -42};
+  test_int<CharT>(check, check_exception, std::span{input});
+}
+
+//
+// Floating point
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_floating_point(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[-42.5, 0, 1.25, 42.5]     "), SV("{:27}"), input);
+  check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<27}"), input);
+  check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^27}"), input);
+  check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>27}"), input);
+
+  check(SV("[-42.5, 0, 1.25, 42.5]     "), SV("{:{}}"), input, 27);
+  check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<{}}"), input, 27);
+  check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^{}}"), input, 27);
+  check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>{}}"), input, 27);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__-42.5, 0, 1.25, 42.5___"), SV("{:_^25n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[-42.5,     0,  1.25,  42.5]"), SV("{::5}"), input);
+  check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<5}"), input);
+  check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^5}"), input);
+  check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>5}"), input);
+
+  check(SV("[-42.5,     0,  1.25,  42.5]"), SV("{::{}}"), input, 5);
+  check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<{}}"), input, 5);
+  check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^{}}"), input, 5);
+  check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>{}}"), input, 5);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::-}"), input);
+  check(SV("[-42.5, +0, +1.25, +42.5]"), SV("{::+}"), input);
+  check(SV("[-42.5,  0,  1.25,  42.5]"), SV("{:: }"), input);
+
+  // *** alternate form ***
+  check(SV("[-42.5, 0., 1.25, 42.5]"), SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check(SV("[-42.5, 00000, 01.25, 042.5]"), SV("{::05}"), input);
+  check(SV("[-42.5, 0000., 01.25, 042.5]"), SV("{::#05}"), input);
+
+  // *** precision ***
+  check(SV("[-42, 0, 1.2, 42]"), SV("{::.2}"), input);
+  check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.3f}"), input);
+
+  check(SV("[-42, 0, 1.2, 42]"), SV("{::.{}}"), input, 2);
+  check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.{}f}"), input, 3);
+
+  check_exception("The format-spec precision field doesn't contain a value or arg-id", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input); // does not require locales present
+#ifndef TEST_HAS_NO_LOCALIZATION
+  std::locale::global(std::locale(LOCALE_fr_FR_UTF_8));
+  check(SV("[-42,5, 0, 1,25, 42,5]"), SV("{::L}"), input);
+
+  std::locale::global(std::locale(LOCALE_en_US_UTF_8));
+  check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input);
+
+  std::locale::global(std::locale::classic());
+#endif // TEST_HAS_NO_LOCALIZATION
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("aAeEfFgG"))
+    check_exception("The format-spec type has a type not supported for a floating-point argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^33::>5}"), input);
+  check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>5}"), input, 33);
+  check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>{}}"), input, 33, 5);
+
+  check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^33::>5.2}"), input);
+  check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>5.2}"), input, 33);
+  check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.2}"), input, 33, 5);
+  check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.{}}"), input, 33, 5, 2);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>5.2}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}.2}"), input, 33);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}.{}}"), input, 33, 5);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_floating_point(TestFunction check, ExceptionTest check_exception) {
+  test_floating_point<CharT>(check, check_exception, std::array{-42.5f, 0.0f, 1.25f, 42.5f});
+  test_floating_point<CharT>(check, check_exception, std::vector{-42.5, 0.0, 1.25, 42.5});
+
+  std::array input{-42.5l, 0.0l, 1.25l, 42.5l};
+  test_floating_point<CharT>(check, check_exception, std::span{input});
+}
+
+//
+// Pointer
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV("[0x0]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[0x0]     "), SV("{:10}"), input);
+  check(SV("[0x0]*****"), SV("{:*<10}"), input);
+  check(SV("__[0x0]___"), SV("{:_^10}"), input);
+  check(SV("#####[0x0]"), SV("{:#>10}"), input);
+
+  check(SV("[0x0]     "), SV("{:{}}"), input, 10);
+  check(SV("[0x0]*****"), SV("{:*<{}}"), input, 10);
+  check(SV("__[0x0]___"), SV("{:_^{}}"), input, 10);
+  check(SV("#####[0x0]"), SV("{:#>{}}"), input, 10);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("_0x0_"), SV("{:_^5n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[  0x0]"), SV("{::5}"), input);
+  check(SV("[0x0**]"), SV("{::*<5}"), input);
+  check(SV("[_0x0_]"), SV("{::_^5}"), input);
+  check(SV("[::0x0]"), SV("{:::>5}"), input);
+
+  check(SV("[  0x0]"), SV("{::{}}"), input, 5);
+  check(SV("[0x0**]"), SV("{::*<{}}"), input, 5);
+  check(SV("[_0x0_]"), SV("{::_^{}}"), input, 5);
+  check(SV("[::0x0]"), SV("{:::>{}}"), input, 5);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input);
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("p"))
+    check_exception("The format-spec type has a type not supported for a pointer argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input);
+  check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12);
+  check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5);
+
+  check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input);
+  check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12);
+  check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>5}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 12);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_pointer(TestFunction check, ExceptionTest check_exception) {
+  test_pointer<CharT>(check, check_exception, std::array{nullptr});
+  test_pointer<CharT>(check, check_exception, std::array{static_cast<const void*>(0)});
+  test_pointer<CharT>(check, check_exception, std::array{static_cast<void*>(0)});
+}
+
+//
+// String
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  check(SV(R"(["Hello", "world"])"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV(R"(["Hello", "world"]     )"), SV("{:23}"), input);
+  check(SV(R"(["Hello", "world"]*****)"), SV("{:*<23}"), input);
+  check(SV(R"(__["Hello", "world"]___)"), SV("{:_^23}"), input);
+  check(SV(R"(#####["Hello", "world"])"), SV("{:#>23}"), input);
+
+  check(SV(R"(["Hello", "world"]     )"), SV("{:{}}"), input, 23);
+  check(SV(R"(["Hello", "world"]*****)"), SV("{:*<{}}"), input, 23);
+  check(SV(R"(__["Hello", "world"]___)"), SV("{:_^{}}"), input, 23);
+  check(SV(R"(#####["Hello", "world"])"), SV("{:#>{}}"), input, 23);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV(R"([Hello   , world   ])"), SV("{::8}"), input);
+  check(SV(R"([Hello***, world***])"), SV("{::*<8}"), input);
+  check(SV(R"([_Hello__, _world__])"), SV("{::_^8}"), input);
+  check(SV(R"([:::Hello, :::world])"), SV("{:::>8}"), input);
+
+  check(SV(R"([Hello   , world   ])"), SV("{::{}}"), input, 8);
+  check(SV(R"([Hello***, world***])"), SV("{::*<{}}"), input, 8);
+  check(SV(R"([_Hello__, _world__])"), SV("{::_^{}}"), input, 8);
+  check(SV(R"([:::Hello, :::world])"), SV("{:::>{}}"), input, 8);
+
+  check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input);
+
+  // *** precision ***
+  check(SV(R"([Hel, wor])"), SV("{::.3}"), input);
+
+  check(SV(R"([Hel, wor])"), SV("{::.{}}"), input, 3);
+
+  check_exception("The format-spec precision field doesn't contain a value or arg-id", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input);
+
+  // *** type ***
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("s?"))
+    check_exception("The format-spec type has a type not supported for a string argument", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8);
+
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25);
+  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}::>8}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 25);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_string(TestFunction check, ExceptionTest check_exception) {
+  test_string<CharT>(check, check_exception, std::array{CSTR("Hello"), CSTR("world")});
+  test_string<CharT>(check, check_exception, std::array{STR("Hello"), STR("world")});
+  test_string<CharT>(check, check_exception, std::array{SV("Hello"), SV("world")});
+}
+
+//
+// Handle
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_status(TestFunction check, ExceptionTest check_exception) {
+  std::array input{status::foo, status::bar, status::foobar};
+
+  check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[0xaaaa, 0x5555, 0xaa55]     "), SV("{:29}"), input);
+  check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<29}"), input);
+  check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^29}"), input);
+  check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>29}"), input);
+
+  check(SV("[0xaaaa, 0x5555, 0xaa55]     "), SV("{:{}}"), input, 29);
+  check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<{}}"), input, 29);
+  check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^{}}"), input, 29);
+  check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>{}}"), input, 29);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__0xaaaa, 0x5555, 0xaa55___"), SV("{:_^27n}"), input);
+
+  // *** type ***
+  check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check_exception("The format-spec type has a type not supported for a status argument", SV("{::*<7}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("sxX"))
+    check_exception("The format-spec type has a type not supported for a status argument", fmt, input);
+
+  check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{::x}"), input);
+  check(SV("[0XAAAA, 0X5555, 0XAA55]"), SV("{::X}"), input);
+  check(SV("[foo, bar, foobar]"), SV("{::s}"), input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^29:X}"), input);
+  check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^{}:X}"), input, 29);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}:X}"), input);
+}
+
+//
+// Pair
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_pair_tuple(TestFunction check, ExceptionTest check_exception, auto&& input) {
+  // [format.range.formatter]/3
+  //   For range_formatter<T, charT>, the format-spec in a
+  //   range-underlying-spec, if any, is interpreted by formatter<T, charT>.
+  //
+  //   template<class ParseContext>
+  //   constexpr typename ParseContext::iterator
+  //    parse(ParseContext& ctx);
+  // [format.tuple]/7
+  //   ... if e.set_debug_format() is a valid expression, calls
+  //   e.set_debug_format().
+  // So when there is no range-underlying-spec, there is no need to call parse
+  // thus the char element is not escaped.
+  // TODO FMT P2733 addresses this issue.
+  check(SV("[(1, a), (42, *)]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[(1, a), (42, *)]     "), SV("{:22}"), input);
+  check(SV("[(1, a), (42, *)]*****"), SV("{:*<22}"), input);
+  check(SV("__[(1, a), (42, *)]___"), SV("{:_^22}"), input);
+  check(SV("#####[(1, a), (42, *)]"), SV("{:#>22}"), input);
+
+  check(SV("[(1, a), (42, *)]     "), SV("{:{}}"), input, 22);
+  check(SV("[(1, a), (42, *)]*****"), SV("{:*<{}}"), input, 22);
+  check(SV("__[(1, a), (42, *)]___"), SV("{:_^{}}"), input, 22);
+  check(SV("#####[(1, a), (42, *)]"), SV("{:#>{}}"), input, 22);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__(1, a), (42, *)___"), SV("{:_^20n}"), input);
+  check(SV("__(1, a), (42, *)___"), SV("{:_^20nm}"), input); // m should have no effect
+
+  // *** type ***
+  check(SV("__{(1, a), (42, *)}___"), SV("{:_^22m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[(1, 'a')   , (42, '*')  ]"), SV("{::11}"), input);
+  check(SV("[(1, 'a')***, (42, '*')**]"), SV("{::*<11}"), input);
+  check(SV("[_(1, 'a')__, _(42, '*')_]"), SV("{::_^11}"), input);
+  check(SV("[###(1, 'a'), ##(42, '*')]"), SV("{::#>11}"), input);
+
+  check(SV("[(1, 'a')   , (42, '*')  ]"), SV("{::{}}"), input, 11);
+  check(SV("[(1, 'a')***, (42, '*')**]"), SV("{::*<{}}"), input, 11);
+  check(SV("[_(1, 'a')__, _(42, '*')_]"), SV("{::_^{}}"), input, 11);
+  check(SV("[###(1, 'a'), ##(42, '*')]"), SV("{::#>{}}"), input, 11);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:::<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input);
+
+  // *** type ***
+  check(SV("[1: 'a', 42: '*']"), SV("{::m}"), input);
+  check(SV("[1, 'a', 42, '*']"), SV("{::n}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>(""))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^31:#>11}"), input);
+  check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^31:#>11}"), input);
+  check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^{}:#>11}"), input, 31);
+  check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^{}:#>{}}"), input, 31, 11);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}:#>5}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}:#>{}}"), input, 31);
+
+  check(SV("1: 'a', 42: '*'"), SV("{:n:m}"), input);
+  check(SV("1, 'a', 42, '*'"), SV("{:n:n}"), input);
+  check(SV("{1: 'a', 42: '*'}"), SV("{:m:m}"), input);
+  check(SV("{1, 'a', 42, '*'}"), SV("{:m:n}"), input);
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_pair_tuple(TestFunction check, ExceptionTest check_exception) {
+  test_pair_tuple<CharT>(
+      check, check_exception, std::array{std::make_pair(1, CharT('a')), std::make_pair(42, CharT('*'))});
+  test_pair_tuple<CharT>(
+      check, check_exception, std::array{std::make_tuple(1, CharT('a')), std::make_tuple(42, CharT('*'))});
+}
+
+//
+// Tuple 1
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_tuple_int(TestFunction check, ExceptionTest check_exception) {
+  std::array input{std::make_tuple(42), std::make_tuple(99)};
+
+  check(SV("[(42), (99)]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[(42), (99)]     "), SV("{:17}"), input);
+  check(SV("[(42), (99)]*****"), SV("{:*<17}"), input);
+  check(SV("__[(42), (99)]___"), SV("{:_^17}"), input);
+  check(SV("#####[(42), (99)]"), SV("{:#>17}"), input);
+
+  check(SV("[(42), (99)]     "), SV("{:{}}"), input, 17);
+  check(SV("[(42), (99)]*****"), SV("{:*<{}}"), input, 17);
+  check(SV("__[(42), (99)]___"), SV("{:_^{}}"), input, 17);
+  check(SV("#####[(42), (99)]"), SV("{:#>{}}"), input, 17);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__(42), (99)___"), SV("{:_^15n}"), input);
+
+  // *** type ***
+  check(SV("__{(42), (99)}___"), SV("{:_^17m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[(42)   , (99)   ]"), SV("{::7}"), input);
+  check(SV("[(42)***, (99)***]"), SV("{::*<7}"), input);
+  check(SV("[_(42)__, _(99)__]"), SV("{::_^7}"), input);
+  check(SV("[###(42), ###(99)]"), SV("{::#>7}"), input);
+
+  check(SV("[(42)   , (99)   ]"), SV("{::{}}"), input, 7);
+  check(SV("[(42)***, (99)***]"), SV("{::*<{}}"), input, 7);
+  check(SV("[_(42)__, _(99)__]"), SV("{::_^{}}"), input, 7);
+  check(SV("[###(42), ###(99)]"), SV("{::#>{}}"), input, 7);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:::<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input);
+
+  // *** type ***
+  check(SV("[42, 99]"), SV("{::n}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>(""))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^23:#>7}"), input);
+  check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^23:#>7}"), input);
+  check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^{}:#>7}"), input, 23);
+  check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^{}:#>{}}"), input, 23, 7);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}:#>5}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}:#>{}}"), input, 23);
+}
+
+//
+// Tuple 3
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_tuple_int_int_int(TestFunction check, ExceptionTest check_exception) {
+  std::array input{std::make_tuple(42, 99, 0), std::make_tuple(1, 10, 100)};
+
+  check(SV("[(42, 99, 0), (1, 10, 100)]"), SV("{}"), input);
+
+  // ***** underlying has no format-spec
+
+  // *** align-fill & width ***
+  check(SV("[(42, 99, 0), (1, 10, 100)]     "), SV("{:32}"), input);
+  check(SV("[(42, 99, 0), (1, 10, 100)]*****"), SV("{:*<32}"), input);
+  check(SV("__[(42, 99, 0), (1, 10, 100)]___"), SV("{:_^32}"), input);
+  check(SV("#####[(42, 99, 0), (1, 10, 100)]"), SV("{:#>32}"), input);
+
+  check(SV("[(42, 99, 0), (1, 10, 100)]     "), SV("{:{}}"), input, 32);
+  check(SV("[(42, 99, 0), (1, 10, 100)]*****"), SV("{:*<{}}"), input, 32);
+  check(SV("__[(42, 99, 0), (1, 10, 100)]___"), SV("{:_^{}}"), input, 32);
+  check(SV("#####[(42, 99, 0), (1, 10, 100)]"), SV("{:#>{}}"), input, 32);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input);
+
+  // *** n
+  check(SV("__(42, 99, 0), (1, 10, 100)___"), SV("{:_^30n}"), input);
+
+  // *** type ***
+  check(SV("__{(42, 99, 0), (1, 10, 100)}___"), SV("{:_^32m}"), input);
+  check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input);
+  check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Only underlying has a format-spec
+  check(SV("[(42, 99, 0)   , (1, 10, 100)  ]"), SV("{::14}"), input);
+  check(SV("[(42, 99, 0)***, (1, 10, 100)**]"), SV("{::*<14}"), input);
+  check(SV("[_(42, 99, 0)__, _(1, 10, 100)_]"), SV("{::_^14}"), input);
+  check(SV("[###(42, 99, 0), ##(1, 10, 100)]"), SV("{::#>14}"), input);
+
+  check(SV("[(42, 99, 0)   , (1, 10, 100)  ]"), SV("{::{}}"), input, 14);
+  check(SV("[(42, 99, 0)***, (1, 10, 100)**]"), SV("{::*<{}}"), input, 14);
+  check(SV("[_(42, 99, 0)__, _(1, 10, 100)_]"), SV("{::_^{}}"), input, 14);
+  check(SV("[###(42, 99, 0), ##(1, 10, 100)]"), SV("{::#>{}}"), input, 14);
+
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{:::<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::}<}"), input);
+  check_exception("The format-spec range-fill field contains an invalid character", SV("{::{<}"), input);
+
+  // *** sign ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::+}"), input);
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{:: }"), input);
+
+  // *** alternate form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input);
+
+  // *** zero-padding ***
+  check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input);
+
+  // *** precision ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input);
+
+  // *** locale-specific form ***
+  check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input);
+
+  // *** type ***
+  check(SV("[42, 99, 0, 1, 10, 100]"), SV("{::n}"), input);
+  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("s"))
+    check_exception("The format-spec should consume the input or end with a '}'", fmt, input);
+
+  // ***** Both have a format-spec
+  check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^37:#>14}"), input);
+  check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^37:#>14}"), input);
+  check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^{}:#>14}"), input, 37);
+  check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^{}:#>{}}"), input, 37, 14);
+
+  check_exception("Argument index out of bounds", SV("{:^^{}:#>5}"), input);
+  check_exception("Argument index out of bounds", SV("{:^^{}:#>{}}"), input, 37);
+}
+
+//
+// Ranges
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_with_ranges(TestFunction check, ExceptionTest check_exception, auto&& iter) {
+  std::ranges::subrange range{std::move(iter), std::default_sentinel};
+  test_int<CharT>(check, check_exception, std::move(range));
+}
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void test_with_ranges(TestFunction check, ExceptionTest check_exception) {
+  std::array input{1, 2, 42, -42};
+  test_with_ranges<CharT>(
+      check, check_exception, std::counted_iterator{cpp20_input_iterator<int*>(input.data()), input.size()});
+  test_with_ranges<CharT>(
+      check, check_exception, std::counted_iterator{forward_iterator<int*>(input.data()), input.size()});
+  test_with_ranges<CharT>(
+      check, check_exception, std::counted_iterator{bidirectional_iterator<int*>(input.data()), input.size()});
+  test_with_ranges<CharT>(
+      check, check_exception, std::counted_iterator{random_access_iterator<int*>(input.data()), input.size()});
+  test_with_ranges<CharT>(
+      check, check_exception, std::counted_iterator{contiguous_iterator<int*>(input.data()), input.size()});
+}
+
+//
+// Driver
+//
+
+template <class CharT, class TestFunction, class ExceptionTest>
+void format_tests(TestFunction check, ExceptionTest check_exception) {
+  test_char<CharT>(check, check_exception);
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+  if (std::same_as<CharT, wchar_t>) // avoid testing twice
+    test_char_to_wchar(check, check_exception);
+#endif
+  test_bool<CharT>(check, check_exception);
+  test_int<CharT>(check, check_exception);
+  test_floating_point<CharT>(check, check_exception);
+  test_pointer<CharT>(check, check_exception);
+  test_string<CharT>(check, check_exception);
+
+  test_status<CharT>(check, check_exception); // Has its own handler with its own parser
+
+  test_pair_tuple<CharT>(check, check_exception);
+  test_tuple_int<CharT>(check, check_exception);
+  test_tuple_int_int_int<CharT>(check, check_exception);
+
+  test_with_ranges<CharT>(check, check_exception);
+}
+
+#endif // TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FORMATTER_FORMAT_FUNCTIONS_TESTS_H

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp
new file mode 100644
index 0000000000000..c4738936c81ae
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <format>
+
+//  template<class T, class charT = char>
+//    requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+//  class range_formatter;
+
+// string vformat(string_view fmt, format_args args);
+// wstring vformat(wstring_view fmt, wformat_args args);
+
+#include <format>
+#include <cassert>
+
+#include "format.functions.tests.h"
+#include "test_macros.h"
+#include "assert_macros.h"
+
+auto test = []<class CharT, class... Args>(
+                std::basic_string_view<CharT> expected, std::basic_string_view<CharT> fmt, Args&&... args) {
+  std::basic_string<CharT> out = std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
+  TEST_REQUIRE(
+      out == expected,
+      test_concat_message("\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
+};
+
+auto test_exception =
+    []<class CharT, class... Args>(
+        [[maybe_unused]] std::string_view what,
+        [[maybe_unused]] std::basic_string_view<CharT> fmt,
+        [[maybe_unused]] Args&&... args) {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+      try {
+        TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args<context_t<CharT>>(args...));
+        TEST_FAIL(test_concat_message("\nFormat string   ", fmt, "\nDidn't throw an exception.\n"));
+      } catch (const std::format_error& e) {
+        TEST_LIBCPP_REQUIRE(
+            e.what() == what,
+            test_concat_message(
+                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
+
+        return;
+      }
+      assert(false);
+#endif
+    };
+
+int main(int, char**) {
+  format_tests<char>(test, test_exception);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  format_tests<wchar_t>(test, test_exception);
+#endif
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.pass.cpp
new file mode 100644
index 0000000000000..acfe8cc9ac413
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <format>
+
+// template<class T, class charT = char>
+//   requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+// class range_formatter
+
+// template<class FormatContext>
+//   typename FormatContext::iterator
+//     format(const T& ref, FormatContext& ctx) const;
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format functions test.
+
+#include <cassert>
+#include <concepts>
+#include <format>
+#include <vector>
+
+#include "test_format_context.h"
+#include "test_macros.h"
+#include "make_string.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class StringViewT>
+void test_format(StringViewT expected, std::vector<int> arg) {
+  using CharT      = typename StringViewT::value_type;
+  using String     = std::basic_string<CharT>;
+  using OutIt      = std::back_insert_iterator<String>;
+  using FormatCtxT = std::basic_format_context<OutIt, CharT>;
+
+  const std::range_formatter<int, CharT> formatter;
+
+  String result;
+  OutIt out             = std::back_inserter(result);
+  FormatCtxT format_ctx = test_format_context_create<OutIt, CharT>(out, std::make_format_args<FormatCtxT>(arg));
+  formatter.format(arg, format_ctx);
+  assert(result == expected);
+}
+
+template <class CharT>
+void test_fmt() {
+  test_format(SV("[1]"), std::vector<int>{1});
+  test_format(SV("[0]"), std::vector<int>{0});
+}
+
+void test() {
+  test_fmt<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_fmt<wchar_t>();
+#endif
+}
+
+int main(int, char**) {
+  test();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/parse.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/parse.pass.cpp
new file mode 100644
index 0000000000000..ce1c0c93130b6
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/parse.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <format>
+
+// template<class T, class charT = char>
+//   requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+// class range_formatter
+
+// template<class ParseContext>
+//   constexpr typename ParseContext::iterator
+//     parse(ParseContext& ctx);
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format functions test.
+
+#include <cassert>
+#include <concepts>
+#include <format>
+
+#include "test_format_context.h"
+#include "test_macros.h"
+#include "make_string.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class StringViewT>
+constexpr void test_parse(StringViewT fmt) {
+  using CharT    = typename StringViewT::value_type;
+  auto parse_ctx = std::basic_format_parse_context<CharT>(fmt);
+  std::range_formatter<int, CharT> formatter;
+  static_assert(std::semiregular<decltype(formatter)>);
+
+  std::same_as<typename StringViewT::iterator> auto it = formatter.parse(parse_ctx);
+  assert(it == fmt.end() - (!fmt.empty() && fmt.back() == '}'));
+}
+
+template <class CharT>
+constexpr void test_fmt() {
+  test_parse(SV(""));
+  test_parse(SV(":d"));
+
+  test_parse(SV("}"));
+  test_parse(SV(":d}"));
+}
+
+constexpr bool test() {
+  test_fmt<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_fmt<wchar_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp
new file mode 100644
index 0000000000000..fe2d72cb249ba
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <format>
+
+// template<class T, class charT = char>
+//   requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+// class range_formatter
+
+// constexpr void constexpr void set_brackets(basic_string_view<charT> opening,
+//                                            basic_string_view<charT> closing);
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format functions test.
+
+#include <format>
+#include <cassert>
+#include <type_traits>
+#include <vector>
+
+#include "make_string.h"
+#include "test_format_context.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class CharT>
+constexpr void test_setter() {
+  std::range_formatter<int, CharT> formatter;
+  formatter.set_brackets(SV("open"), SV("close"));
+
+  // Note there is no direct way to validate this function modified the object.
+  if (!std::is_constant_evaluated()) {
+    using String     = std::basic_string<CharT>;
+    using OutIt      = std::back_insert_iterator<String>;
+    using FormatCtxT = std::basic_format_context<OutIt, CharT>;
+
+    String result;
+    OutIt out             = std::back_inserter(result);
+    FormatCtxT format_ctx = test_format_context_create<OutIt, CharT>(out, std::make_format_args<FormatCtxT>());
+    formatter.format(std::vector<int>{0, 42, 99}, format_ctx);
+    assert(result == SV("open0, 42, 99close"));
+  }
+}
+
+constexpr bool test() {
+  test_setter<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_setter<wchar_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp
new file mode 100644
index 0000000000000..413f5b4669a49
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <format>
+
+// template<class T, class charT = char>
+//   requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+// class range_formatter
+
+// constexpr void set_separator(basic_string_view<charT> sep);
+
+// Note this tests the basics of this function. It's tested in more detail in
+// the format functions test.
+
+#include <format>
+#include <cassert>
+#include <type_traits>
+#include <vector>
+
+#include "make_string.h"
+#include "test_format_context.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+template <class CharT>
+constexpr void test_setter() {
+  std::range_formatter<int, CharT> formatter;
+  formatter.set_separator(SV("sep"));
+
+  // Note there is no direct way to validate this function modified the object.
+  if (!std::is_constant_evaluated()) {
+    using String     = std::basic_string<CharT>;
+    using OutIt      = std::back_insert_iterator<String>;
+    using FormatCtxT = std::basic_format_context<OutIt, CharT>;
+
+    String result;
+    OutIt out             = std::back_inserter(result);
+    FormatCtxT format_ctx = test_format_context_create<OutIt, CharT>(out, std::make_format_args<FormatCtxT>());
+    formatter.format(std::vector<int>{0, 42, 99}, format_ctx);
+    assert(result == SV("[0sep42sep99]"));
+  }
+}
+
+constexpr bool test() {
+  test_setter<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_setter<wchar_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp
new file mode 100644
index 0000000000000..7d819d4ec6c37
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
+// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}
+
+// <format>
+
+// template<class T, class charT = char>
+//   requires same_as<remove_cvref_t<T>, T> && formattable<T, charT>
+// class range_formatter
+
+// constexpr formatter<T, charT>& underlying();
+// constexpr const formatter<T, charT>& underlying() const;
+
+#include <concepts>
+#include <format>
+
+#include "test_macros.h"
+
+template <class CharT>
+constexpr void test_underlying() {
+  {
+    std::range_formatter<int, CharT> formatter;
+    [[maybe_unused]] std::same_as<std::formatter<int, CharT>&> decltype(auto) underlying = formatter.underlying();
+  }
+  {
+    const std::range_formatter<int, CharT> formatter;
+    [[maybe_unused]] std::same_as<const std::formatter<int, CharT>&> decltype(auto) underlying = formatter.underlying();
+  }
+}
+
+constexpr bool test() {
+  test_underlying<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_underlying<wchar_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp b/libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp
index d6f9bd376849c..6f64016cfd57b 100644
--- a/libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.tuple/format.functions.format.pass.cpp
@@ -8,6 +8,9 @@
 // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
 // UNSUPPORTED: libcpp-has-no-incomplete-format
 
+// TODO FMT Fix this test using GCC, it currently times out.
+// UNSUPPORTED: gcc-12
+
 // This test requires the dylib support introduced in D92214.
 // XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}}
 // XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}}

diff  --git a/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h b/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h
index 2ad2d879d4e07..3f7a1fa032201 100644
--- a/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h
+++ b/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h
@@ -279,7 +279,7 @@ template <class CharT, class TestFunction, class ExceptionTest, class Nested>
 void test_nested(TestFunction check, ExceptionTest check_exception, Nested&& input) {
   // [format.formatter.spec]/2
   //   A debug-enabled specialization of formatter additionally provides a
-  //   public, constexpr, non-static member function set_­debug_­format()
+  //   public, constexpr, non-static member function set_debug_format()
   //   which modifies the state of the formatter to be as if the type of the
   //   std-format-spec parsed by the last call to parse were ?.
   // pair and tuple are not debug-enabled specializations to the

diff  --git a/libcxx/test/support/format.functions.common.h b/libcxx/test/support/format.functions.common.h
index 14faf3acd0d5d..9796c87f56b86 100644
--- a/libcxx/test/support/format.functions.common.h
+++ b/libcxx/test/support/format.functions.common.h
@@ -13,6 +13,7 @@
 #include <algorithm>
 #include <charconv>
 #include <format>
+#include <ranges>
 
 #include "make_string.h"
 
@@ -129,4 +130,57 @@ struct std::formatter<status, CharT> {
   }
 };
 
+// Creates format string for the invalid types.
+//
+// valid contains a list of types that are valid.
+// - The type ?s is the only type requiring 2 characters, use S for that type.
+// - Whether n is a type or not depends on the context, is is always used.
+//
+// The return value is a collection of basic_strings, instead of
+// basic_string_views since the values are temporaries.
+namespace detail {
+template <class CharT, size_t N>
+std::basic_string<CharT> get_colons() {
+  static std::basic_string<CharT> result(N, CharT(':'));
+  return result;
+}
+
+constexpr std::string_view get_format_types() {
+  return "aAbBcdeEfFgGopsxX"
+#if TEST_STD_VER > 20
+         "?"
+#endif
+      ;
+}
+
+template <class CharT, /*format_types types,*/ size_t N>
+std::vector<std::basic_string<CharT>> fmt_invalid_types(std::string_view valid) {
+  // std::ranges::to is not available in C++20.
+  std::vector<std::basic_string<CharT>> result;
+  std::ranges::copy(
+      get_format_types() | std::views::filter([&](char type) { return valid.find(type) == std::string_view::npos; }) |
+          std::views::transform([&](char type) { return std::format(SV("{{{}{}}}"), get_colons<CharT, N>(), type); }),
+      std::back_inserter(result));
+  return result;
+}
+
+} // namespace detail
+
+// Creates format string for the invalid types.
+//
+// valid contains a list of types that are valid.
+//
+// The return value is a collection of basic_strings, instead of
+// basic_string_views since the values are temporaries.
+template <class CharT>
+std::vector<std::basic_string<CharT>> fmt_invalid_types(std::string_view valid) {
+  return detail::fmt_invalid_types<CharT, 1>(valid);
+}
+
+// Like fmt_invalid_types but when the format spec is for an underlying formatter.
+template <class CharT>
+std::vector<std::basic_string<CharT>> fmt_invalid_nested_types(std::string_view valid) {
+  return detail::fmt_invalid_types<CharT, 2>(valid);
+}
+
 #endif // TEST_SUPPORT_FORMAT_FUNCTIONS_COMMON_H


        


More information about the libcxx-commits mailing list