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