[PATCH v4] libstdc++: Implement debug format for strings and characters formatters [PR109162]
Tomasz Kaminski
tkaminsk@redhat.com
Thu Apr 10 17:52:44 GMT 2025
On Thu, Apr 10, 2025 at 6:39 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> On 10/04/25 11:24 +0200, Tomasz Kamiński wrote:
> >This patch implements part P2286R8 that specified debug (escaped)
> >format for the strings and characters sequences. This include both
> >handling of the '?' format specifier and set_debug_format member.
> >
> >To indicate partial support we define __glibcxx_format_ranges macro
> >value 1, without defining __cpp_lib_format_ranges.
> >
> >We provide two separate escaping routines depending on the literal
> >encoding for the corresponding character types. If the character
> >encoding is Unicode, we follow the specification for the standard
> >(__format::__write_escaped_unicode).
> >For other encodings, we escape only characters in range [0x00, 0x80),
> >interpreting them as ACII values: [0x00, 0x20), 0x7f and '\t', '\r',
>
> "ASCII"
>
> >'\n', '\\', '"', '\'' are escaped. We assume every character outside
> >this range is printable (__format::_write_escaped_ascii).
> >In particular we do not yet implement special handling of shift
> >sequences.
> >
> >For Unicode escaping a new __escape_edges table is introduced,
> >that encodes information if character belongs to General_Category
> >that is escaped by the standard (Control or Other). This table
> >is generated from DerivedGeneralCategory.txt provided by Unicode.
> >Only boolean flag is preserved to reduce the number of entires.
>
> "entries"
>
> >The additional rules for escaping are handled by __should_escape_unicode.
> >
> >When width or precision is specified, we emit escaped string to the
> temporary
> >buffer and format the resulting string according to the format spec.
> >For characters use a fixed size stack buffer, for which a new
> _Fixedbuf_sink is
> >introduced. For strings, we use _Str_sink and to avoid allocations,
> >we compute the estimated size of (possibly truncated) input, and if it is
> >larger than width field we print directly.
> >
> > PR libstdc++/109162
> >
> >contrib/ChangeLog:
> >
> > * unicode/README: Mentioned DerivedGeneralCategory.txt.
> > * unicode/gen_libstdcxx_unicode_data.py: Generation __escape_edges
> > table from DerivedGeneralCategory.txt. Update file name in
> comments.
> > * unicode/DerivedGeneralCategory.txt: Copy of file distrubuted by
>
> "distributed"
>
> > Unicode Consortium.
> >
> ftp://ftp.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt.
>
>
> I still don't think we want the URL in the ChangeLog.
>
> >libstdc++-v3/ChangeLog:
> >
> > * include/bits/chrono_io.h (__detail::_Widen): Moved to std/format
> file.
> > * include/bits/unicode-data.h: Regnerate.
> > * include/bits/unicode.h (__unicode::_Utf_iterator::_M_units)
> > (__unicode::__should_escape_category): Define.
>
> What happened to the changes to bits/version.def and bits/version.h ?
>
> I thought you were going to change version.def to use no_stdname but
> now it's not in the patch at all.
>
The are already in master, as I merged them with formatter for
vector<bool>::reference:
https://gcc.gnu.org/cgit/gcc/commit/?id=84d668b0ca67c5d3fe6430f101d61e60aa796a81
>
> > * include/std/format (_GLIBCXX_WIDEN_, _GLIBCXX_WIDEN): Copied from
> > include/bits/chrono_io.h.
> > (__format::_Widen): Moved from include/bits/chrono_io.h.
> > (__format::_Term_char, __format::_Escapes, __format::_Separators)
> > (__format::__should_escape_ascii,
> __format::__should_escape_unicode)
> > (__format::__write_escape_seq, __format::__write_escaped_char)
> > (__format::__write_escaped_acii, __format::__write_escaped_unicode)
> > (__format::__write_escaped): Define.
> > (__formatter_str::_S_format): Extracted truncation of character
> > sequences.
> > (__formatter_str::format): Handle _Pres_esc.
> > (__formatter_int::_M_do_parse) [__glibcxx_format_ranges]: Parse
> '?'.
> > (__formatter_int::_M_format_character_escaped): Define.
> > (formatter<_CharT, _CharT>::format, formatter<char,
> wchar_t>::format):
> > Handle _Pres_esc.
> > (__formatter_str::set_debug_format,
> formatter<...>::set_debug_format)
> > Guard with __glibcxx_format_ranges.
> > (__format::_Fixedbuf_sink): Define.
> > * testsuite/std/format/debug.cc: New test.
> > * testsuite/std/format/debug_nonunicode.cc: New test.
> > * testsuite/std/format/parse_ctx.cc (escaped_strings_supported):
> Define
> > to true if __glibcxx_format_ranges is defined.
> > * testsuite/std/format/string.cc (escaped_strings_supported):
> Define to
> > true if __glibcxx_format_ranges is defined.
> >---
> > This I believe address all review suggestions. I have also followed
> > Patrick's suggestions and added debug_nonunicode.cc test file.
> > Which helped to surface problem, where _GLIBCXX_WIDEN("\FFFFD") was
> > ill-formed for non-unicode encodings.
> >
> > contrib/unicode/DerivedGeneralCategory.txt | 4323 +++++++++++++++++
> > contrib/unicode/README | 3 +-
> > contrib/unicode/gen_libstdcxx_unicode_data.py | 47 +-
> > libstdc++-v3/include/bits/chrono_io.h | 16 +-
> > libstdc++-v3/include/bits/unicode-data.h | 260 +-
> > libstdc++-v3/include/bits/unicode.h | 17 +
> > libstdc++-v3/include/std/format | 492 +-
> > .../23_containers/vector/bool/format.cc | 3 +-
> > libstdc++-v3/testsuite/std/format/debug.cc | 454 ++
> > .../testsuite/std/format/debug_nounicode.cc | 5 +
> > .../testsuite/std/format/parse_ctx.cc | 2 +-
> > libstdc++-v3/testsuite/std/format/string.cc | 2 +-
> > 12 files changed, 5538 insertions(+), 86 deletions(-)
> > create mode 100644 contrib/unicode/DerivedGeneralCategory.txt
> > create mode 100644 libstdc++-v3/testsuite/std/format/debug.cc
> > create mode 100644 libstdc++-v3/testsuite/std/format/debug_nounicode.cc
>
> [snip]
>
> >diff --git a/libstdc++-v3/include/bits/unicode.h
> b/libstdc++-v3/include/bits/unicode.h
> >index 99d972eccff..f1b6bf49c54 100644
> >--- a/libstdc++-v3/include/bits/unicode.h
> >+++ b/libstdc++-v3/include/bits/unicode.h
> >@@ -150,6 +150,11 @@ namespace __unicode
> > base() const requires forward_iterator<_Iter>
> > { return _M_curr(); }
> >
> >+ [[nodiscard]]
> >+ constexpr iter_difference_t<_Iter>
> >+ _M_units() const requires forward_iterator<_Iter>
> >+ { return _M_to_increment; }
> >+
> > [[nodiscard]]
> > constexpr value_type
> > operator*() const { return _M_buf[_M_buf_index]; }
> >@@ -609,6 +614,18 @@ inline namespace __v16_0_0
> > return (__p - __width_edges) % 2 + 1;
> > }
> >
> >+ // @pre c <= 0x10FFFF
> >+ constexpr bool
> >+ __should_escape_category(char32_t __c) noexcept
> >+ {
> >+ constexpr uint32_t __mask = 0x01;
> >+ auto* __end = std::end(__escape_edges);
> >+ auto* __p = std::lower_bound(__escape_edges, __end,
> >+ (__c << 1u) + 2);
> >+ return __p[-1] & __mask;
> >+ }
> >+
> >+
> > // @pre c <= 0x10FFFF
> > constexpr _Gcb_property
> > __grapheme_cluster_break_property(char32_t __c) noexcept
> >diff --git a/libstdc++-v3/include/std/format
> b/libstdc++-v3/include/std/format
> >index 2e9319cdda6..b905d8c012d 100644
> >--- a/libstdc++-v3/include/std/format
> >+++ b/libstdc++-v3/include/std/format
> >@@ -80,8 +80,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > /// @cond undocumented
> > namespace __format
> > {
> >- // Type-erased character sink.
> >+ // STATICALLY-WIDEN, see C++20 [time.general]
> >+ // It doesn't matter for format strings (which can only be char or
> wchar_t)
> >+ // but this returns the narrow string for anything that isn't wchar_t.
> This
> >+ // is done because const char* can be inserted into any ostream type,
> and
> >+ // will be widened at runtime if necessary.
> >+ template<typename _CharT>
> >+ consteval auto
> >+ _Widen(const char* __narrow, const wchar_t* __wide)
> >+ {
> >+ if constexpr (is_same_v<_CharT, wchar_t>)
> >+ return __wide;
> >+ else
> >+ return __narrow;
> >+ }
> >+#define _GLIBCXX_WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
> >+#define _GLIBCXX_WIDEN(S) _GLIBCXX_WIDEN_(_CharT, S)
> >+
> >+ // Type-erased character sinks.
> > template<typename _CharT> class _Sink;
> >+ template<typename _CharT> class _Fixedbuf_sink;
> >+ template<typename _Seq> class _Seq_sink;
> >+
> >+ template<typename _CharT, typename _Alloc = allocator<_CharT>>
> >+ using _Str_sink
> >+ = _Seq_sink<basic_string<_CharT, char_traits<_CharT>, _Alloc>>;
> >+
> >+ // template<typename _CharT, typename _Alloc = allocator<_CharT>>
> >+ // using _Vec_sink = _Seq_sink<vector<_CharT, _Alloc>>;
> >+
> > // Output iterator that writes to a type-erase character sink.
> > template<typename _CharT>
> > class _Sink_iter;
> >@@ -848,6 +875,286 @@ namespace __format
> > __spec._M_fill);
> > }
> >
> >+ // Valus are indicies into _Escapes::all.
>
> "Values" and "indices"
>
> >+ enum class _Term_char : unsigned char {
> >+ _Tc_quote = 12,
> >+ _Tc_apos = 15
> >+ };
> >+
> >+ template<typename _CharT>
> >+ struct _Escapes
> >+ {
> >+ using _Str_view = basic_string_view<_CharT>;
> >+
> >+ static consteval
> >+ _Str_view _S_all()
> >+ { return _GLIBCXX_WIDEN("\t\\t\n\\n\r\\r\\\\\\\"\\\"'\\'\\u\\x"); }
> >+
> >+ static constexpr
> >+ _CharT _S_term(_Term_char __term)
> >+ { return _S_all()[static_cast<unsigned char>(__term)]; }
> >+
> >+ static consteval
> >+ _Str_view _S_tab()
> >+ { return _S_all().substr(0, 3); }
> >+
> >+ static consteval
> >+ _Str_view _S_newline()
> >+ { return _S_all().substr(3, 3); }
> >+
> >+ static consteval
> >+ _Str_view _S_return()
> >+ { return _S_all().substr(6, 3); }
> >+
> >+ static consteval
> >+ _Str_view _S_bslash()
> >+ { return _S_all().substr(9, 3); }
> >+
> >+ static consteval
> >+ _Str_view _S_quote()
> >+ { return _S_all().substr(12, 3); }
> >+
> >+ static consteval
> >+ _Str_view _S_apos()
> >+ { return _S_all().substr(15, 3); }
> >+
> >+ static consteval
> >+ _Str_view _S_u()
> >+ { return _S_all().substr(18, 2); }
> >+
> >+ static consteval
> >+ _Str_view _S_x()
> >+ { return _S_all().substr(20, 2); }
> >+ };
> >+
> >+ template<typename _CharT>
> >+ struct _Separators
> >+ {
> >+ using _Str_view = basic_string_view<_CharT>;
> >+
> >+ static consteval
> >+ _Str_view _S_all()
> >+ { return _GLIBCXX_WIDEN("{}"); }
> >+
> >+ static consteval
> >+ _Str_view _S_braces()
> >+ { return _S_all().substr(0, 2); }
> >+ };
> >+
> >+ template<typename _CharT>
> >+ constexpr bool __should_escape_ascii(_CharT __c, _Term_char __term)
> >+ {
> >+ using _Esc = _Escapes<_CharT>;
> >+ switch (__c)
> >+ {
> >+ case _Esc::_S_tab()[0]:
> >+ case _Esc::_S_newline()[0]:
> >+ case _Esc::_S_return()[0]:
> >+ case _Esc::_S_bslash()[0]:
> >+ return true;
> >+ case _Esc::_S_quote()[0]:
> >+ return __term == _Term_char::_Tc_quote;
> >+ case _Esc::_S_apos()[0]:
> >+ return __term == _Term_char::_Tc_apos;
> >+ default:
> >+ return (__c >= 0 && __c < 0x20) || __c == 0x7f;
> >+ };
> >+ }
> >+
> >+ // @pre __c <= 0x10FFFF
> >+ constexpr bool __should_escape_unicode(char32_t __c, bool __prev_esc)
> >+ {
> >+ if (__unicode::__should_escape_category(__c))
> >+ return __c != U' ';
> >+ if (!__prev_esc)
> >+ return false;
> >+ return __unicode::__grapheme_cluster_break_property(__c)
> >+ == __unicode::_Gcb_property::_Gcb_Extend;
> >+ }
> >+
> >+ using uint_least32_t = __UINT_LEAST32_TYPE__;
> >+ template<typename _Out, typename _CharT>
> >+ _Out
> >+ __write_escape_seq(_Out __out, uint_least32_t __val,
> >+ basic_string_view<_CharT> __prefix)
> >+ {
> >+ using _Str_view = basic_string_view<_CharT>;
> >+ constexpr size_t __max = 8;
> >+ char __buf[__max];
> >+ const string_view __narrow(
> >+ __buf,
> >+ std::__to_chars_i<uint_least32_t>(__buf, __buf + __max, __val,
> 16).ptr);
> >+
> >+ __out = __format::__write(__out, __prefix);
> >+ *__out = _Separators<_CharT>::_S_braces()[0];
> >+ ++__out;
> >+ if constexpr (is_same_v<char, _CharT>)
> >+ __out = __format::__write(__out, __narrow);
> >+#ifdef _GLIBCXX_USE_WCHAR_T
> >+ else
> >+ {
> >+ _CharT __wbuf[__max];
> >+ const size_t __n = __narrow.size();
> >+ std::__to_wstring_numeric(__narrow.data(), __n, __wbuf);
> >+ __out = __format::__write(__out, _Str_view(__wbuf, __n));
> >+ }
> >+#endif
> >+ *__out = _Separators<_CharT>::_S_braces()[1];
> >+ return ++__out;
> >+ }
> >+
> >+ template<typename _Out, typename _CharT>
> >+ _Out
> >+ __write_escaped_char(_Out __out, _CharT __c)
> >+ {
> >+ using _UChar = make_unsigned_t<_CharT>;
> >+ using _Esc = _Escapes<_CharT>;
> >+ switch (__c)
> >+ {
> >+ case _Esc::_S_tab()[0]:
> >+ return __format::__write(__out, _Esc::_S_tab().substr(1, 2));
> >+ case _Esc::_S_newline()[0]:
> >+ return __format::__write(__out, _Esc::_S_newline().substr(1,
> 2));
> >+ case _Esc::_S_return()[0]:
> >+ return __format::__write(__out, _Esc::_S_return().substr(1,
> 2));
> >+ case _Esc::_S_bslash()[0]:
> >+ return __format::__write(__out, _Esc::_S_bslash().substr(1,
> 2));
> >+ case _Esc::_S_quote()[0]:
> >+ return __format::__write(__out, _Esc::_S_quote().substr(1, 2));
> >+ case _Esc::_S_apos()[0]:
> >+ return __format::__write(__out, _Esc::_S_apos().substr(1, 2));
> >+ default:
> >+ return __format::__write_escape_seq(__out,
> >+ static_cast<_UChar>(__c),
> >+ _Esc::_S_u());
> >+ }
> >+ }
> >+
> >+ template<typename _CharT, typename _Out>
> >+ _Out
> >+ __write_escaped_ascii(_Out __out,
> >+ basic_string_view<_CharT> __str,
> >+ _Term_char __term)
> >+ {
> >+ using _Str_view = basic_string_view<_CharT>;
> >+ auto __first = __str.begin();
> >+ auto const __last = __str.end();
> >+ while (__first != __last)
> >+ {
> >+ auto __print = __first;
> >+ // assume anything outside ASCII is printable
> >+ while (__print != __last
> >+ && !__format::__should_escape_ascii(*__print, __term))
> >+ ++__print;
> >+
> >+ if (__print != __first)
> >+ __out = __format::__write(__out, _Str_view(__first, __print));
> >+
> >+ if (__print == __last)
> >+ return __out;
> >+
> >+ __first = __print;
> >+ __out = __format::__write_escaped_char(__out, *__first);
> >+ ++__first;
> >+ }
> >+ return __out;
> >+ }
> >+
> >+ template<typename _CharT, typename _Out>
> >+ _Out
> >+ __write_escaped_unicode(_Out __out,
> >+ basic_string_view<_CharT> __str,
> >+ _Term_char __term)
> >+ {
> >+ using _Str_view = basic_string_view<_CharT>;
> >+ using _UChar = make_unsigned_t<_CharT>;
> >+ using _Esc = _Escapes<_CharT>;
> >+
> >+ static constexpr char32_t __replace = U'\uFFFD';
> >+ static constexpr _Str_view __replace_rep = []
> >+ {
> >+ // N.B. "\uFFFD" is ill-formed if encoding is not unicode.
> >+ if constexpr (is_same_v<char, _CharT>)
> >+ return "\xEF\xBF\xBD";
> >+ else
> >+ return L"\xFFFD";
> >+ }();
> >+
> >+ __unicode::_Utf_view<char32_t, _Str_view> __v(std::move(__str));
> >+ auto __first = __v.begin();
> >+ auto const __last = __v.end();
> >+
> >+ bool __prev_esc = true;
> >+ while (__first != __last)
> >+ {
> >+ bool __esc_ascii = false;
> >+ bool __esc_unicode = false;
> >+ bool __esc_replace = false;
> >+ auto __should_escape = [&](auto const& __it)
> >+ {
> >+ if (*__it <= 0x7f)
> >+ return __esc_ascii
> >+ = __format::__should_escape_ascii(*__it.base(),
> __term);
> >+ if (__format::__should_escape_unicode(*__it, __prev_esc))
> >+ return __esc_unicode = true;
> >+ if (*__it == __replace)
> >+ {
> >+ _Str_view __units(__it.base(), __it._M_units());
> >+ return __esc_replace = (__units != __replace_rep);
> >+ }
> >+ return false;
> >+ };
> >+
> >+ auto __print = __first;
> >+ while (__print != __last && !__should_escape(__print))
> >+ {
> >+ __prev_esc = false;
> >+ ++__print;
> >+ }
> >+
> >+ if (__print != __first)
> >+ __out = __format::__write(__out, _Str_view(__first.base(),
> __print.base()));
> >+
> >+ if (__print == __last)
> >+ return __out;
> >+
> >+ __first = __print;
> >+ if (__esc_ascii)
> >+ __out = __format::__write_escaped_char(__out, *__first.base());
> >+ else if (__esc_unicode)
> >+ __out = __format::__write_escape_seq(__out, *__first,
> _Esc::_S_u());
> >+ else // __esc_replace
> >+ for (_CharT __c : _Str_view(__first.base(),
> __first._M_units()))
> >+ __out = __format::__write_escape_seq(__out,
> >+
> static_cast<_UChar>(__c),
> >+ _Esc::_S_x());
> >+ __prev_esc = true;
> >+ ++__first;
> >+
> >+ }
> >+ return __out;
> >+ }
> >+
> >+ template<typename _CharT, typename _Out>
> >+ _Out
> >+ __write_escaped(_Out __out, basic_string_view<_CharT> __str,
> _Term_char __term)
> >+ {
> >+ *__out = _Escapes<_CharT>::_S_term(__term);
> >+ ++__out;
> >+
> >+ if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>())
> >+ __out = __format::__write_escaped_unicode(__out, __str, __term);
> >+ else if constexpr (is_same_v<char, _CharT>
> >+ &&
> __unicode::__literal_encoding_is_extended_ascii())
> >+ __out = __format::__write_escaped_ascii(__out, __str, __term);
> >+ else
> >+ // TODO Handle non-ascii extended encoding
> >+ __out = __format::__write_escaped_ascii(__out, __str, __term);
> >+
> >+ *__out = _Escapes<_CharT>::_S_term(__term);
> >+ return ++__out;
> >+ }
> >+
> > // A lightweight optional<locale>.
> > struct _Optional_locale
> > {
> >@@ -961,7 +1268,7 @@ namespace __format
> >
> > if (*__first == 's')
> > ++__first;
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > else if (*__first == '?')
> > {
> > __spec._M_type = _Pres_esc;
> >@@ -980,43 +1287,71 @@ namespace __format
> > format(basic_string_view<_CharT> __s,
> > basic_format_context<_Out, _CharT>& __fc) const
> > {
> >- if (_M_spec._M_type == _Pres_esc)
> >+ constexpr auto __term = __format::_Term_char::_Tc_quote;
> >+ const auto __write_direct = [&]
> > {
> >- // TODO: C++23 escaped string presentation
> >- }
> >+ if (_M_spec._M_type == _Pres_esc)
> >+ return __format::__write_escaped(__fc.out(), __s, __term);
> >+ else
> >+ return __format::__write(__fc.out(), __s);
> >+ };
> >
> > if (_M_spec._M_width_kind == _WP_none
> > && _M_spec._M_prec_kind == _WP_none)
> >- return __format::__write(__fc.out(), __s);
> >+ return __write_direct();
> >
> >- size_t __estimated_width;
> >- if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>())
> >- {
> >- if (_M_spec._M_prec_kind != _WP_none)
> >- {
> >- size_t __prec = _M_spec._M_get_precision(__fc);
> >- __estimated_width = __unicode::__truncate(__s, __prec);
> >- }
> >- else
> >- __estimated_width = __unicode::__field_width(__s);
> >- }
> >- else
> >- {
> >- __s = __s.substr(0, _M_spec._M_get_precision(__fc));
> >- __estimated_width = __s.size();
> >- }
> >+ const size_t __prec =
> >+ _M_spec._M_prec_kind != _WP_none
> >+ ? _M_spec._M_get_precision(__fc)
> >+ : basic_string_view<_CharT>::npos;
> >
> >- return __format::__write_padded_as_spec(__s, __estimated_width,
> >+ const size_t __estimated_width = _S_trunc(__s, __prec);
> >+ // N.B. Escaping only increases width
> >+ if (_M_spec._M_get_width(__fc) <= __estimated_width
> >+ && _M_spec._M_prec_kind == _WP_none)
> >+ return __write_direct();
> >+
> >+ if (_M_spec._M_type != _Pres_esc)
> >+ return __format::__write_padded_as_spec(__s, __estimated_width,
> >+ __fc, _M_spec);
> >+
> >+ __format::_Str_sink<_CharT> __sink;
> >+ __format::_Sink_iter<_CharT> __out(__sink);
> >+ __format::__write_escaped(__out, __s, __term);
> >+ basic_string_view<_CharT> __escaped(__sink.view().data(),
> >+ __sink.view().size());
> >+ const size_t __escaped_width = _S_trunc(__escaped, __prec);
> >+ // N.B. [tab:format.type.string] defines '?' as
> >+ // Copies the escaped string ([format.string.escaped]) to the
> output,
> >+ // so precision seem to appy to escaped string.
> >+ return __format::__write_padded_as_spec(__escaped,
> __escaped_width,
> > __fc, _M_spec);
> > }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void
> > set_debug_format() noexcept
> > { _M_spec._M_type = _Pres_esc; }
> > #endif
> >
> > private:
> >+ static size_t
> >+ _S_trunc(basic_string_view<_CharT>& __s, size_t __prec)
> >+ {
> >+ if constexpr (__unicode::__literal_encoding_is_unicode<_CharT>())
> >+ {
> >+ if (__prec != basic_string_view<_CharT>::npos)
> >+ return __unicode::__truncate(__s, __prec);
> >+ else
> >+ return __unicode::__field_width(__s);
> >+ }
> >+ else
> >+ {
> >+ __s = __s.substr(0, __prec);
> >+ return __s.size();
> >+ }
> >+ }
> >+
> > _Spec<_CharT> _M_spec{};
> > };
> >
> >@@ -1120,7 +1455,7 @@ namespace __format
> > ++__first;
> > }
> > break;
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > case '?':
> > if (__type == _AsChar)
> > {
> >@@ -1272,7 +1607,7 @@ namespace __format
> > _S_character_width(_CharT __c)
> > {
> > // N.B. single byte cannot encode charcter of width greater than 1
> >- if constexpr (sizeof(_CharT) > 1u &&
> >+ if constexpr (sizeof(_CharT) > 1u &&
> > __unicode::__literal_encoding_is_unicode<_CharT>())
> > return __unicode::__field_width(__c);
> > else
> >@@ -1286,7 +1621,34 @@ namespace __format
> > {
> > return __format::__write_padded_as_spec({&__c, 1u},
> > _S_character_width(__c),
> >- __fc, _M_spec);
> >+ __fc, _M_spec);
> >+ }
> >+
> >+ template<typename _Out>
> >+ typename basic_format_context<_Out, _CharT>::iterator
> >+ _M_format_character_escaped(_CharT __c,
> >+ basic_format_context<_Out, _CharT>&
> __fc) const
> >+ {
> >+ using _Esc = _Escapes<_CharT>;
> >+ constexpr auto __term = __format::_Term_char::_Tc_apos;
> >+ const basic_string_view<_CharT> __in(&__c, 1u);
> >+ if (_M_spec._M_get_width(__fc) <= 3u)
> >+ return __format::__write_escaped(__fc.out(), __in, __term);
> >+
> >+ _CharT __buf[12];
> >+ __format::_Fixedbuf_sink<_CharT> __sink(__buf);
> >+ __format::_Sink_iter<_CharT> __out(__sink);
> >+ __format::__write_escaped(__out, __in, __term);
> >+
> >+ const basic_string_view<_CharT> __escaped = __sink.view();
> >+ size_t __estimated_width;
> >+ if (__escaped[1] == _Esc::_S_bslash()[0]) // escape sequence
> >+ __estimated_width = __escaped.size();
> >+ else
> >+ __estimated_width = 2 + _S_character_width(__c);
> >+ return __format::__write_padded_as_spec(__escaped,
> >+ __estimated_width,
> >+ __fc, _M_spec);
> > }
> >
> > template<typename _Int>
> >@@ -1973,15 +2335,12 @@ namespace __format
> > || _M_f._M_spec._M_type == __format::_Pres_c)
> > return _M_f._M_format_character(__u, __fc);
> > else if (_M_f._M_spec._M_type == __format::_Pres_esc)
> >- {
> >- // TODO
> >- return __fc.out();
> >- }
> >+ return _M_f._M_format_character_escaped(__u, __fc);
> > else
> > return _M_f.format(static_cast<make_unsigned_t<_CharT>>(__u),
> __fc);
> > }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void
> > set_debug_format() noexcept
> > { _M_f._M_spec._M_type = __format::_Pres_esc; }
> >@@ -2012,15 +2371,12 @@ namespace __format
> > || _M_f._M_spec._M_type == __format::_Pres_c)
> > return _M_f._M_format_character(__u, __fc);
> > else if (_M_f._M_spec._M_type == __format::_Pres_esc)
> >- {
> >- // TODO
> >- return __fc.out();
> >- }
> >+ return _M_f._M_format_character_escaped(__u, __fc);
> > else
> > return _M_f.format(static_cast<unsigned char>(__u), __fc);
> > }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void
> > set_debug_format() noexcept
> > { _M_f._M_spec._M_type = __format::_Pres_esc; }
> >@@ -2050,7 +2406,7 @@ namespace __format
> > format(_CharT* __u, basic_format_context<_Out, _CharT>& __fc) const
> > { return _M_f.format(__u, __fc); }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void set_debug_format() noexcept {
> _M_f.set_debug_format(); }
> > #endif
> >
> >@@ -2075,7 +2431,7 @@ namespace __format
> > basic_format_context<_Out, _CharT>& __fc) const
> > { return _M_f.format(__u, __fc); }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void set_debug_format() noexcept {
> _M_f.set_debug_format(); }
> > #endif
> >
> >@@ -2099,7 +2455,7 @@ namespace __format
> > basic_format_context<_Out, _CharT>& __fc) const
> > { return _M_f.format({__u, _Nm}, __fc); }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void set_debug_format() noexcept {
> _M_f.set_debug_format(); }
> > #endif
> >
> >@@ -2123,7 +2479,7 @@ namespace __format
> > basic_format_context<_Out, char>& __fc) const
> > { return _M_f.format(__u, __fc); }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void set_debug_format() noexcept {
> _M_f.set_debug_format(); }
> > #endif
> >
> >@@ -2148,7 +2504,7 @@ namespace __format
> > basic_format_context<_Out, wchar_t>& __fc) const
> > { return _M_f.format(__u, __fc); }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void set_debug_format() noexcept {
> _M_f.set_debug_format(); }
> > #endif
> >
> >@@ -2173,7 +2529,7 @@ namespace __format
> > basic_format_context<_Out, char>& __fc) const
> > { return _M_f.format(__u, __fc); }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void set_debug_format() noexcept {
> _M_f.set_debug_format(); }
> > #endif
> >
> >@@ -2198,7 +2554,7 @@ namespace __format
> > basic_format_context<_Out, wchar_t>& __fc) const
> > { return _M_f.format(__u, __fc); }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
> > constexpr void set_debug_format() noexcept {
> _M_f.set_debug_format(); }
> > #endif
> >
> >@@ -2859,6 +3215,32 @@ namespace __format
> > { return _Sink_iter<_CharT>(*this); }
> > };
> >
> >+
> >+ template<typename _CharT>
> >+ class _Fixedbuf_sink final : public _Sink<_CharT>
> >+ {
> >+ void
> >+ _M_overflow() override
> >+ {
> >+ __glibcxx_assert(false);
> >+ this->_M_rewind();
> >+ }
> >+
> >+ public:
> >+ [[__gnu__::__always_inline__]]
> >+ constexpr explicit
> >+ _Fixedbuf_sink(span<_CharT> __buf)
> >+ : _Sink<_CharT>(__buf)
> >+ { }
> >+
> >+ constexpr basic_string_view<_CharT>
> >+ view() const
> >+ {
> >+ auto __s = this->_M_used();
> >+ return basic_string_view<_CharT>(__s.data(), __s.size());
> >+ }
> >+ };
> >+
> > // A sink with an internal buffer. This is used to implement concrete
> sinks.
> > template<typename _CharT>
> > class _Buf_sink : public _Sink<_CharT>
> >@@ -2993,13 +3375,6 @@ namespace __format
> > }
> > };
> >
> >- template<typename _CharT, typename _Alloc = allocator<_CharT>>
> >- using _Str_sink
> >- = _Seq_sink<basic_string<_CharT, char_traits<_CharT>, _Alloc>>;
> >-
> >- // template<typename _CharT, typename _Alloc = allocator<_CharT>>
> >- // using _Vec_sink = _Seq_sink<vector<_CharT, _Alloc>>;
> >-
> > // A sink that writes to an output iterator.
> > // Writes to a fixed-size buffer and then flushes to the output
> iterator
> > // when the buffer fills up.
> >@@ -3675,17 +4050,17 @@ namespace __format
> > return _M_visit([&__vis]<typename _Tp>(_Tp& __val) ->
> decltype(auto)
> > {
> > constexpr bool __user_facing = __is_one_of<_Tp,
> >- monostate, bool, _CharT,
> >- int, unsigned int, long long int, unsigned long long int,
> >- float, double, long double,
> >- const _CharT*, basic_string_view<_CharT>,
> >- const void*, handle>::value;
> >+ monostate, bool, _CharT,
> >+ int, unsigned int, long long int, unsigned long long int,
> >+ float, double, long double,
> >+ const _CharT*, basic_string_view<_CharT>,
> >+ const void*, handle>::value;
> > if constexpr (__user_facing)
> > return std::forward<_Visitor>(__vis)(__val);
> > else
> > {
> >- handle __h(__val);
> >- return std::forward<_Visitor>(__vis)(__h);
> >+ handle __h(__val);
> >+ return std::forward<_Visitor>(__vis)(__h);
> > }
> > }, __type);
> > }
> >@@ -4781,6 +5156,7 @@ namespace __format
> > : __format::__range_default_formatter<format_kind<_Rg>, _Rg, _CharT>
> > { };
> > #endif // C++23 formatting ranges
> >+#undef _GLIBCXX_WIDEN
> >
> > _GLIBCXX_END_NAMESPACE_VERSION
> > } // namespace std
> >diff --git a/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
> b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
> >index 16f6e86dcee..2586225dd05 100644
> >--- a/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
> >+++ b/libstdc++-v3/testsuite/23_containers/vector/bool/format.cc
> >@@ -3,7 +3,6 @@
> >
> > #include <format>
> > #include <vector>
> >-#include <chrono> // For _Widen
> > #include <testsuite_hooks.h>
> >
> > static_assert(!std::formattable<std::vector<bool>::reference, int>);
> >@@ -21,7 +20,7 @@ is_format_string_for(const char* str, Args&&... args)
> > }
> > }
> >
> >-#define WIDEN_(C, S) ::std::chrono::__detail::_Widen<C>(S, L##S)
> >+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
> > #define WIDEN(S) WIDEN_(_CharT, S)
> >
> > void
> >diff --git a/libstdc++-v3/testsuite/std/format/debug.cc
> b/libstdc++-v3/testsuite/std/format/debug.cc
> >new file mode 100644
> >index 00000000000..07cd1e0e349
> >--- /dev/null
> >+++ b/libstdc++-v3/testsuite/std/format/debug.cc
> >@@ -0,0 +1,454 @@
> >+// { dg-options "-fexec-charset=UTF-8 -fwide-exec-charset=UTF-32LE
> -DUNICODE_ENC" }
> >+// { dg-do run { target c++23 } }
> >+// { dg-add-options no_pch }
> >+
> >+#include <format>
> >+#include <testsuite_hooks.h>
> >+
> >+std::string
> >+fdebug(char t)
> >+{ return std::format("{:?}", t); }
> >+
> >+std::wstring
> >+fdebug(wchar_t t)
> >+{ return std::format(L"{:?}", t); }
> >+
> >+std::string
> >+fdebug(std::string_view t)
> >+{ return std::format("{:?}", t); }
> >+
> >+std::wstring
> >+fdebug(std::wstring_view t)
> >+{ return std::format(L"{:?}", t); }
> >+
> >+
> >+#define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S)
> >+#define WIDEN(S) WIDEN_(_CharT, S)
> >+
> >+template<typename _CharT>
> >+void
> >+test_basic_escapes()
> >+{
> >+ std::basic_string<_CharT> res;
> >+
> >+ const auto tab = WIDEN("\t");
> >+ res = fdebug(tab);
> >+ VERIFY( res == WIDEN(R"("\t")") );
> >+ res = fdebug(tab[0]);
> >+ VERIFY( res == WIDEN(R"('\t')") );
> >+
> >+ const auto nline = WIDEN("\n");
> >+ res = fdebug(nline);
> >+ VERIFY( res == WIDEN(R"("\n")") );
> >+ res = fdebug(nline[0]);
> >+ VERIFY( res == WIDEN(R"('\n')") );
> >+
> >+ const auto carret = WIDEN("\r");
> >+ res = fdebug(carret);
> >+ VERIFY( res == WIDEN(R"("\r")") );
> >+ res = fdebug(carret[0]);
> >+ VERIFY( res == WIDEN(R"('\r')") );
> >+
> >+ const auto bslash = WIDEN("\\");
> >+ res = fdebug(bslash);
> >+ VERIFY( res == WIDEN(R"("\\")") );
> >+ res = fdebug(bslash[0]);
> >+ VERIFY( res == WIDEN(R"('\\')") );
> >+
> >+ const auto quote = WIDEN("\"");
> >+ res = fdebug(quote);
> >+ VERIFY( res == WIDEN(R"("\"")") );
> >+ res = fdebug(quote[0]);
> >+ VERIFY( res == WIDEN(R"('"')") );
> >+
> >+ const auto apos = WIDEN("\'");
> >+ res = fdebug(apos);
> >+ VERIFY( res == WIDEN(R"("'")") );
> >+ res = fdebug(apos[0]);
> >+ VERIFY( res == WIDEN(R"('\'')") );
> >+}
> >+
> >+template<typename _CharT>
> >+void
> >+test_ascii_escapes()
> >+{
> >+ std::basic_string<_CharT> res;
> >+
> >+ const auto in = WIDEN("\x10 abcde\x7f\t0123");
> >+ res = fdebug(in);
> >+ VERIFY( res == WIDEN(R"("\u{10} abcde\u{7f}\t0123")") );
> >+ res = fdebug(in[0]);
> >+ VERIFY( res == WIDEN(R"('\u{10}')") );
> >+ res = fdebug(in[1]);
> >+ VERIFY( res == WIDEN(R"(' ')") );
> >+ res = fdebug(in[2]);
> >+ VERIFY( res == WIDEN(R"('a')") );
> >+}
> >+
> >+template<typename _CharT>
> >+void
> >+test_extended_ascii()
> >+{
> >+ std::basic_string<_CharT> res;
> >+
> >+ const auto in = WIDEN("Åëÿ");
> >+ res = fdebug(in);
> >+ VERIFY( res == WIDEN(R"("Åëÿ")") );
> >+
> >+ static constexpr bool __test_characters
> >+#if UNICODE_ENC
> >+ = sizeof(_CharT) >= 2;
> >+#else // ISO8859-1
> >+ = true;
> >+#endif // UNICODE_ENC
> >+
> >+ if constexpr (__test_characters)
> >+ {
> >+ res = fdebug(in[0]);
> >+ VERIFY( res == WIDEN(R"('Å')") );
> >+ res = fdebug(in[1]);
> >+ VERIFY( res == WIDEN(R"('ë')") );
> >+ res = fdebug(in[2]);
> >+ VERIFY( res == WIDEN(R"('ÿ')") );
> >+ }
> >+}
> >+
> >+#if UNICODE_ENC
> >+template<typename _CharT>
> >+void
> >+test_unicode_escapes()
> >+{
> >+ std::basic_string<_CharT> res;
> >+
> >+ const auto in = WIDEN(
> >+ "\u008a" // Cc, Control, Line Tabulation Set,
> >+ "\u00ad" // Cf, Format, Soft Hyphen
> >+ "\u1d3d" // Lm, Modifier letter, Modifier Letter Capital Ou
> >+ "\u00a0" // Zs, Space Separator, No-Break Space (NBSP)
> >+ "\u2029" // Zp, Paragraph Separator, Paragraph Separator
> >+ "\U0001f984" // So, Other Symbol, Unicorn Face
> >+ );
> >+ const auto out = WIDEN("\""
> >+ R"(\u{8a})"
> >+ R"(\u{ad})"
> >+ "\u1d3d"
> >+ R"(\u{a0})"
> >+ R"(\u{2029})"
> >+ "\U0001f984"
> >+ "\"");
> >+
> >+ res = fdebug(in);
> >+ VERIFY( res == out );
> >+
> >+ if constexpr (sizeof(_CharT) >= 2)
> >+ {
> >+ res = fdebug(in[0]);
> >+ VERIFY( res == WIDEN(R"('\u{8a}')") );
> >+ res = fdebug(in[1]);
> >+ VERIFY( res == WIDEN(R"('\u{ad}')") );
> >+ res = fdebug(in[2]);
> >+ VERIFY( res == WIDEN("'\u1d3d'") );
> >+ res = fdebug(in[3]);
> >+ VERIFY( res == WIDEN(R"('\u{a0}')") );
> >+ res = fdebug(in[4]);
> >+ VERIFY( res == WIDEN(R"('\u{2029}')") );
> >+ }
> >+
> >+ if constexpr (sizeof(_CharT) >= 4)
> >+ {
> >+ res = fdebug(in[5]);
> >+ VERIFY( res == WIDEN("'\U0001f984'") );
> >+ }
> >+}
> >+
> >+template<typename _CharT>
> >+void
> >+test_grapheme_extend()
> >+{
> >+ std::basic_string<_CharT> res;
> >+
> >+ const auto vin = WIDEN("o\u0302\u0323");
> >+ res = fdebug(vin);
> >+ VERIFY( res == WIDEN("\"o\u0302\u0323\"") );
> >+
> >+ std::basic_string_view<_CharT> in = WIDEN("\t\u0302\u0323");
> >+ res = fdebug(in);
> >+ VERIFY( res == WIDEN(R"("\t\u{302}\u{323}")") );
> >+
> >+ res = fdebug(in.substr(1));
> >+ VERIFY( res == WIDEN(R"("\u{302}\u{323}")") );
> >+
> >+ if constexpr (sizeof(_CharT) >= 2)
> >+ {
> >+ res = fdebug(in[1]);
> >+ VERIFY( res == WIDEN(R"('\u{302}')") );
> >+ }
> >+}
> >+
> >+template<typename _CharT>
> >+void
> >+test_replacement_char()
> >+{
> >+ std::basic_string<_CharT> repl = WIDEN("\uFFFD");
> >+ std::basic_string<_CharT> res = fdebug(repl);
> >+ VERIFY( res == WIDEN("\"\uFFFD\"") );
> >+
> >+ repl = WIDEN("\uFFFD\uFFFD");
> >+ res = fdebug(repl);
> >+ VERIFY( res == WIDEN("\"\uFFFD\uFFFD\"") );
> >+}
> >+
> >+void
> >+test_ill_formed_utf8_seq()
> >+{
> >+ std::string_view seq = "\xf0\x9f\xa6\x84"; // \U0001F984
> >+ std::string res;
> >+
> >+ res = fdebug(seq);
> >+ VERIFY( res == "\"\U0001F984\"" );
> >+
> >+ res = fdebug(seq.substr(1));
> >+ VERIFY( res == R"("\x{9f}\x{a6}\x{84}")" );
> >+
> >+ res = fdebug(seq.substr(2));
> >+ VERIFY( res == R"("\x{a6}\x{84}")" );
> >+
> >+ res = fdebug(seq[0]);
> >+ VERIFY( res == R"('\x{f0}')" );
> >+ res = fdebug(seq.substr(0, 1));
> >+ VERIFY( res == R"("\x{f0}")" );
> >+
> >+ res = fdebug(seq[1]);
> >+ VERIFY( res == R"('\x{9f}')" );
> >+ res = fdebug(seq.substr(1, 1));
> >+ VERIFY( res == R"("\x{9f}")" );
> >+
> >+ res = fdebug(seq[2]);
> >+ VERIFY( res == R"('\x{a6}')" );
> >+ res = fdebug(seq.substr(2, 1));
> >+ VERIFY( res == R"("\x{a6}")" );
> >+
> >+ res = fdebug(seq[3]);
> >+ VERIFY( res == R"('\x{84}')" );
> >+ res = fdebug(seq.substr(3, 1));
> >+ VERIFY( res == R"("\x{84}")" );
> >+}
> >+
> >+void
> >+test_ill_formed_utf32()
> >+{
> >+ std::wstring res;
> >+
> >+ wchar_t ic1 = static_cast<wchar_t>(0xff'ffff);
> >+ res = fdebug(ic1);
> >+ VERIFY( res == LR"('\x{ffffff}')" );
> >+
> >+ std::wstring is1(1, ic1);
> >+ res = fdebug(is1);
> >+ VERIFY( res == LR"("\x{ffffff}")" );
> >+
> >+ wchar_t ic2 = static_cast<wchar_t>(0xffff'ffff);
> >+ res = fdebug(ic2);
> >+ VERIFY( res == LR"('\x{ffffffff}')" );
> >+
> >+ std::wstring is2(1, ic2);
> >+ res = fdebug(is2);
> >+ VERIFY( res == LR"("\x{ffffffff}")" );
> >+}
> >+#endif // UNICODE_ENC
> >+
> >+template<typename _CharT>
> >+void
> >+test_fill()
> >+{
> >+ std::basic_string<_CharT> res;
> >+
> >+ std::basic_string_view<_CharT> in = WIDEN("a\t\x10\u00ad");
> >+ res = std::format(WIDEN("{:10?}"), in.substr(0, 1));
> >+ VERIFY( res == WIDEN(R"("a" )") );
> >+
> >+ res = std::format(WIDEN("{:->10?}"), in.substr(1, 1));
> >+ VERIFY( res == WIDEN(R"(------"\t")") );
> >+
> >+ res = std::format(WIDEN("{:+<10?}"), in.substr(2, 1));
> >+ VERIFY( res == WIDEN(R"("\u{10}"++)") );
> >+
> >+
> >+ res = std::format(WIDEN("{:10?}"), in[0]);
> >+ VERIFY( res == WIDEN(R"('a' )") );
> >+
> >+ res = std::format(WIDEN("{:->10?}"), in[1]);
> >+ VERIFY( res == WIDEN(R"(------'\t')") );
> >+
> >+ res = std::format(WIDEN("{:+<10?}"), in[2]);
> >+ VERIFY( res == WIDEN(R"('\u{10}'++)") );
> >+
> >+#if UNICODE_ENC
> >+ res = std::format(WIDEN("{:=^10?}"), in.substr(3));
> >+ VERIFY( res == WIDEN(R"(="\u{ad}"=)") );
> >+
> >+ // width is 2
> >+ std::basic_string_view<_CharT> in2 = WIDEN("\u1100");
> >+ res = std::format(WIDEN("{:*^10?}"), in2);
> >+ VERIFY( res == WIDEN("***\"\u1100\"***") );
> >+
> >+ if constexpr (sizeof(_CharT) >= 2)
> >+ {
> >+ res = std::format(WIDEN("{:=^10?}"), in[3]);
> >+ VERIFY( res == WIDEN(R"(='\u{ad}'=)") );
> >+
> >+ res = std::format(WIDEN("{:*^10?}"), in2[0]);
> >+ VERIFY( res == WIDEN("***'\u1100'***") );
> >+ }
> >+#endif // UNICODE_ENC
> >+}
> >+
> >+template<typename _CharT>
> >+void
> >+test_prec()
> >+{
> >+ std::basic_string<_CharT> res;
> >+ // with ? escpaed presentation is copied to ouput, same as source
> >+
> >+ std::basic_string_view<_CharT> in = WIDEN("a\t\x10\u00ad");
> >+ res = std::format(WIDEN("{:.2?}"), in.substr(0, 1));
> >+ VERIFY( res == WIDEN(R"("a)") );
> >+
> >+ res = std::format(WIDEN("{:.4?}"), in.substr(1, 1));
> >+ VERIFY( res == WIDEN(R"("\t")") );
> >+
> >+ res = std::format(WIDEN("{:.5?}"), in.substr(2, 1));
> >+ VERIFY( res == WIDEN(R"("\u{1)") );
> >+
> >+#if UNICODE_ENC
> >+ res = std::format(WIDEN("{:.10?}"), in.substr(3));
> >+ VERIFY( res == WIDEN(R"("\u{ad}")") );
> >+
> >+ std::basic_string_view<_CharT> in2 = WIDEN("\u1100");
> >+ res = std::format(WIDEN("{:.3?}"), in2);
> >+ VERIFY( res == WIDEN("\"\u1100") );
> >+#endif // UNICODE_ENC
> >+}
> >+
> >+void test_char_as_wchar()
> >+{
> >+ std::wstring res;
> >+
> >+ res = std::format(L"{:?}", 'a');
> >+ VERIFY( res == LR"('a')" );
> >+
> >+ res = std::format(L"{:?}", '\t');
> >+ VERIFY( res == LR"('\t')" );
> >+
> >+ res = std::format(L"{:+<10?}", '\x10');
> >+ VERIFY( res == LR"('\u{10}'++)" );
> >+}
> >+
> >+template<typename T>
> >+struct DebugWrapper
> >+{
> >+ T val;
> >+};
> >+
> >+template<typename T, typename CharT>
> >+struct std::formatter<DebugWrapper<T>, CharT>
> >+{
> >+ constexpr std::basic_format_parse_context<CharT>::iterator
> >+ parse(std::basic_format_parse_context<CharT>& pc)
> >+ {
> >+ auto out = under.parse(pc);
> >+ under.set_debug_format();
> >+ return out;
> >+ }
> >+
> >+ template<typename Out>
> >+ Out format(DebugWrapper<T> const& t,
> >+ std::basic_format_context<Out, CharT>& fc) const
> >+ { return under.format(t.val, fc); }
> >+
> >+private:
> >+ std::formatter<T, CharT> under;
> >+};
> >+
> >+template<typename _CharT, typename StrT>
> >+void
> >+test_formatter_str()
> >+{
> >+ _CharT buf[]{ 'a', 'b', 'c', 0 };
> >+ DebugWrapper<StrT> in{ buf };
> >+ std::basic_string<_CharT> res = std::format(WIDEN("{:?}"), in );
> >+ VERIFY( res == WIDEN(R"("abc")") );
> >+}
> >+
> >+template<typename _CharT>
> >+void
> >+test_formatter_arr()
> >+{
> >+ std::basic_string<_CharT> res;
> >+
> >+ DebugWrapper<_CharT[3]> in3{ 'a', 'b', 'c' };
> >+ res = std::format(WIDEN("{:?}"), in3 );
> >+ VERIFY( res == WIDEN(R"("abc")") );
> >+
> >+ // We print all characters, including null-terminator
> >+ DebugWrapper<_CharT[4]> in4{ 'a', 'b', 'c', 0 };
> >+ res = std::format(WIDEN("{:?}"), in4 );
> >+ VERIFY( res == WIDEN(R"("abc\u{0}")") );
> >+}
> >+
> >+template<typename _CharT, typename SrcT>
> >+void
> >+test_formatter_char()
> >+{
> >+ DebugWrapper<SrcT> in{ 'a' };
> >+ std::basic_string<_CharT> res = std::format(WIDEN("{:?}"), in);
> >+ VERIFY( res == WIDEN(R"('a')") );
> >+}
> >+
> >+template<typename CharT>
> >+void
> >+test_formatters()
> >+{
> >+ test_formatter_char<CharT, CharT>();
> >+ test_formatter_str<CharT, CharT*>();
> >+ test_formatter_str<CharT, const CharT*>();
> >+ test_formatter_str<CharT, std::basic_string<CharT>>();
> >+ test_formatter_str<CharT, std::basic_string_view<CharT>>();
> >+ test_formatter_arr<CharT>();
> >+}
> >+
> >+void
> >+test_formatters_c()
> >+{
> >+ test_formatters<char>();
> >+ test_formatters<wchar_t>();
> >+ test_formatter_char<wchar_t, char>();
> >+}
> >+
> >+int main()
> >+{
> >+ test_basic_escapes<char>();
> >+ test_basic_escapes<wchar_t>();
> >+ test_ascii_escapes<char>();
> >+ test_ascii_escapes<wchar_t>();
> >+ test_extended_ascii<char>();
> >+ test_extended_ascii<wchar_t>();
> >+
> >+#if UNICODE_ENC
> >+ test_unicode_escapes<char>();
> >+ test_unicode_escapes<wchar_t>();
> >+ test_grapheme_extend<char>();
> >+ test_grapheme_extend<wchar_t>();
> >+ test_replacement_char<char>();
> >+ test_replacement_char<wchar_t>();
> >+ test_ill_formed_utf8_seq();
> >+ test_ill_formed_utf32();
> >+#endif // UNICODE_ENC
> >+
> >+ test_fill<char>();
> >+ test_fill<wchar_t>();
> >+ test_prec<char>();
> >+ test_prec<wchar_t>();
> >+
> >+ test_formatters_c();
> >+}
> >diff --git a/libstdc++-v3/testsuite/std/format/debug_nounicode.cc
> b/libstdc++-v3/testsuite/std/format/debug_nounicode.cc
> >new file mode 100644
> >index 00000000000..5c03171d71a
> >--- /dev/null
> >+++ b/libstdc++-v3/testsuite/std/format/debug_nounicode.cc
> >@@ -0,0 +1,5 @@
> >+// { dg-options "-fexec-charset=ISO8859-1 -fwide-exec-charset=UTF-32LE" }
> >+// { dg-do run { target c++23 } }
> >+// { dg-add-options no_pch }
> >+
> >+#include "debug.cc"
> >diff --git a/libstdc++-v3/testsuite/std/format/parse_ctx.cc
> b/libstdc++-v3/testsuite/std/format/parse_ctx.cc
> >index b5dd7cdba78..b338ac7b762 100644
> >--- a/libstdc++-v3/testsuite/std/format/parse_ctx.cc
> >+++ b/libstdc++-v3/testsuite/std/format/parse_ctx.cc
> >@@ -108,7 +108,7 @@ is_std_format_spec_for(std::string_view spec)
> > }
> > }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges
> > constexpr bool escaped_strings_supported = true;
> > #else
> > constexpr bool escaped_strings_supported = false;
> >diff --git a/libstdc++-v3/testsuite/std/format/string.cc
> b/libstdc++-v3/testsuite/std/format/string.cc
> >index ee987a15ec3..76614d4bc3e 100644
> >--- a/libstdc++-v3/testsuite/std/format/string.cc
> >+++ b/libstdc++-v3/testsuite/std/format/string.cc
> >@@ -62,7 +62,7 @@ test_indexing()
> > VERIFY( ! is_format_string_for("{} {0}", 1) );
> > }
> >
> >-#if __cpp_lib_format_ranges
> >+#if __glibcxx_format_ranges
> > constexpr bool escaped_strings_supported = true;
> > #else
> > constexpr bool escaped_strings_supported = false;
> >--
> >2.49.0
> >
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250410/13fb0aad/attachment-0001.htm>
More information about the Libstdc++
mailing list