[libc++][format] Adds integer formatter.
Implements the formatter for all fundamental integer types
(except `char`, `wchar_t`, and `bool`).
[format.formatter.spec]/2.3
For each charT, for each cv-unqualified arithmetic type ArithmeticT other
than char, wchar_t, char8_t, char16_t, or char32_t, a specialization
```
template<> struct formatter<ArithmeticT, charT>;
```
This removes the stub implemented in D96664.
As an extension it adds partial support for 128-bit integer types.
Implements parts of:
- P0645 Text Formatting
- P1652 Printf corner cases in std::format
Completes:
- LWG-3248 #b, #B, #o, #x, and #X presentation types misformat negative numbers
Reviewed By: #libc, ldionne, vitaut
Differential Revision: https://reviews.llvm.org/D103433
NOKEYCHECK=True
GitOrigin-RevId: 3e9689d72cdffab9672427c664d699334948088a
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index a884521..3c6b4c6 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -140,6 +140,8 @@
__format/format_parse_context.h
__format/format_string.h
__format/formatter.h
+ __format/formatter_integer.h
+ __format/formatter_integral.h
__format/formatter_string.h
__format/parser_std_format_spec.h
__function_like.h
diff --git a/include/__format/formatter.h b/include/__format/formatter.h
index 9c7cca0..38fd88e 100644
--- a/include/__format/formatter.h
+++ b/include/__format/formatter.h
@@ -148,6 +148,30 @@
}
/**
+ * @overload
+ *
+ * Uses a transformation operation before writing an element.
+ *
+ * TODO FMT Fill will probably change to support Unicode grapheme cluster.
+ */
+template <class _CharT, class _UnaryOperation, class _Fill>
+_LIBCPP_HIDE_FROM_ABI auto
+__write(output_iterator<const _CharT&> auto __out_it, const _CharT* __first,
+ const _CharT* __last, size_t __size, _UnaryOperation __op,
+ size_t __width, _Fill __fill,
+ __format_spec::_Flags::_Alignment __alignment) -> decltype(__out_it) {
+
+ _LIBCPP_ASSERT(__first <= __last, "Not a valid range");
+ _LIBCPP_ASSERT(__size < __width, "Precondition failure");
+
+ __padding_size_result __padding =
+ __padding_size(__size, __width, __alignment);
+ __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before, __fill);
+ __out_it = _VSTD::transform(__first, __last, _VSTD::move(__out_it), __op);
+ return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill);
+}
+
+/**
* Writes Unicode input to the output with the required padding.
*
* This function does almost the same as the @ref __write function, but handles
diff --git a/include/__format/formatter_integer.h b/include/__format/formatter_integer.h
new file mode 100644
index 0000000..767df36
--- /dev/null
+++ b/include/__format/formatter_integer.h
@@ -0,0 +1,170 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___FORMAT_FORMATTER_INTEGER_H
+#define _LIBCPP___FORMAT_FORMATTER_INTEGER_H
+
+#include <__availability>
+#include <__config>
+#include <__format/format_error.h>
+#include <__format/format_fwd.h>
+#include <__format/formatter.h>
+#include <__format/formatter_integral.h>
+#include <__format/parser_std_format_spec.h>
+#include <limits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 17
+
+// TODO FMT Remove this once we require compilers with proper C++20 support.
+// If the compiler has no concepts support, the format header will be disabled.
+// Without concepts support enable_if needs to be used and that too much effort
+// to support compilers with partial C++20 support.
+#if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+namespace __format_spec {
+
+template <class _CharT>
+class _LIBCPP_TEMPLATE_VIS __parser_integer : public __parser_integral<_CharT> {
+public:
+ _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx)
+ -> decltype(__parse_ctx.begin()) {
+ auto __it = __parser_integral<_CharT>::__parse(__parse_ctx);
+
+ switch (this->__type) {
+ case _Flags::_Type::__default:
+ this->__type = _Flags::_Type::__decimal;
+ [[fallthrough]];
+
+ case _Flags::_Type::__binary_lower_case:
+ case _Flags::_Type::__binary_upper_case:
+ case _Flags::_Type::__octal:
+ case _Flags::_Type::__decimal:
+ case _Flags::_Type::__hexadecimal_lower_case:
+ case _Flags::_Type::__hexadecimal_upper_case:
+ this->__handle_integer();
+ break;
+
+ case _Flags::_Type::__char:
+ this->__handle_char();
+ break;
+
+ default:
+ __throw_format_error("The format-spec type has a type not supported for "
+ "an integer argument");
+ }
+ return __it;
+ }
+};
+
+template <class _CharT>
+using __formatter_integer = __formatter_integral<__parser_integer<_CharT>>;
+
+} // namespace __format_spec
+
+// [format.formatter.spec]/2.3
+// For each charT, for each cv-unqualified arithmetic type ArithmeticT other
+// than char, wchar_t, char8_t, char16_t, or char32_t, a specialization
+
+// Signed integral types.
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<signed char, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<short, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<int, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<long, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<long long, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+#ifndef _LIBCPP_HAS_NO_INT128
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<__int128_t, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {
+ using _Base = __format_spec::__formatter_integer<_CharT>;
+
+ _LIBCPP_HIDE_FROM_ABI auto format(__int128_t __value, auto& __ctx)
+ -> decltype(__ctx.out()) {
+ // TODO FMT Implement full 128 bit support.
+ using _To = long long;
+ if (__value < numeric_limits<_To>::min() ||
+ __value > numeric_limits<_To>::max())
+ __throw_format_error("128-bit value is outside of implemented range");
+
+ return _Base::format(static_cast<_To>(__value), __ctx);
+ }
+};
+#endif
+
+// Unsigned integral types.
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<unsigned char, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<unsigned short, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<unsigned, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<unsigned long, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<unsigned long long, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {};
+#ifndef _LIBCPP_HAS_NO_INT128
+template <class _CharT>
+struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
+ formatter<__uint128_t, _CharT>
+ : public __format_spec::__formatter_integer<_CharT> {
+ using _Base = __format_spec::__formatter_integer<_CharT>;
+
+ _LIBCPP_HIDE_FROM_ABI auto format(__uint128_t __value, auto& __ctx)
+ -> decltype(__ctx.out()) {
+ // TODO FMT Implement full 128 bit support.
+ using _To = unsigned long long;
+ if (__value < numeric_limits<_To>::min() ||
+ __value > numeric_limits<_To>::max())
+ __throw_format_error("128-bit value is outside of implemented range");
+
+ return _Base::format(static_cast<_To>(__value), __ctx);
+ }
+};
+#endif
+
+#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+#endif //_LIBCPP_STD_VER > 17
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___FORMAT_FORMATTER_INTEGER_H
diff --git a/include/__format/formatter_integral.h b/include/__format/formatter_integral.h
new file mode 100644
index 0000000..14b4e3a
--- /dev/null
+++ b/include/__format/formatter_integral.h
@@ -0,0 +1,502 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___FORMAT_FORMATTER_INTEGRAL_H
+#define _LIBCPP___FORMAT_FORMATTER_INTEGRAL_H
+
+#include <__config>
+#include <__format/format_error.h>
+#include <__format/format_fwd.h>
+#include <__format/formatter.h>
+#include <__format/parser_std_format_spec.h>
+#include <__algorithm/copy.h>
+#include <__algorithm/copy_n.h>
+#include <__algorithm/fill_n.h>
+#include <__algorithm/transform.h>
+#include <array>
+#include <charconv>
+#include <concepts>
+#include <limits>
+#include <string>
+
+#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+#include <locale>
+#endif
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 17
+
+// TODO FMT Remove this once we require compilers with proper C++20 support.
+// If the compiler has no concepts support, the format header will be disabled.
+// Without concepts support enable_if needs to be used and that too much effort
+// to support compilers with partial C++20 support.
+#if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+/**
+ * Integral formatting classes.
+ *
+ * There are two types used here:
+ * * C++-type, the type as used in C++.
+ * * format-type, the output type specified in the std-format-spec.
+ *
+ * Design of the integral formatters consists of several layers.
+ * * @ref __parser_integral The basic std-format-spec parser for all integral
+ * classes. This parser does the basic sanity checks. It also contains some
+ * helper functions that are nice to have available for all parsers.
+ * * A C++-type specific parser. These parsers must derive from
+ * @ref __parser_integral. Their task is to validate whether the parsed
+ * std-format-spec is valid for the C++-type and selected format-type. After
+ * validation they need to make sure all members are properly set. For
+ * example, when the alignment hasn't changed it needs to set the proper
+ * default alignment for the format-type. The following parsers are available:
+ * - @ref __parser_integer
+ * - @ref __parser_char
+ * - @ref __parser_bool
+ * * A general formatter for all integral types @ref __formatter_integral. This
+ * formatter can handle all formatting of integers and characters. The class
+ * derives from the proper formatter.
+ * Note the boolean string format-type isn't supported in this class.
+ * * A typedef C++-type group combining the @ref __formatter_integral with a
+ * parser:
+ * * @ref __formatter_integer
+ * * @ref __formatter_char
+ * * @ref __formatter_bool
+ * * Then every C++-type has its own formatter specializations. They inherit
+ * from the C++-type group typedef. Most specializations need nothing else.
+ * Others need some additional specializations in this class.
+ */
+namespace __format_spec {
+
+/** Wrapper around @ref to_chars, returning the output pointer. */
+template <class _Tp>
+_LIBCPP_HIDE_FROM_ABI char* __to_buffer(char* __first, char* __last,
+ _Tp __value, int __base) {
+ // TODO FMT Evaluate code overhead due to not calling the internal function
+ // directly. (Should be zero overhead.)
+ to_chars_result __r = _VSTD::to_chars(__first, __last, __value, __base);
+ _LIBCPP_ASSERT(__r.ec == errc(0), "Internal buffer too small");
+ return __r.ptr;
+}
+
+/**
+ * Helper to determine the buffer size to output a integer in Base @em x.
+ *
+ * There are several overloads for the supported bases. The function uses the
+ * base as template argument so it can be used in a constant expression.
+ */
+template <unsigned_integral _Tp, size_t _Base>
+_LIBCPP_HIDE_FROM_ABI constexpr size_t __buffer_size() noexcept
+ requires(_Base == 2) {
+ return numeric_limits<_Tp>::digits // The number of binary digits.
+ + 2 // Reserve space for the '0[Bb]' prefix.
+ + 1; // Reserve space for the sign.
+}
+
+template <unsigned_integral _Tp, size_t _Base>
+_LIBCPP_HIDE_FROM_ABI constexpr size_t __buffer_size() noexcept
+ requires(_Base == 8) {
+ return numeric_limits<_Tp>::digits // The number of binary digits.
+ / 3 // Adjust to octal.
+ + 1 // Turn floor to ceil.
+ + 1 // Reserve space for the '0' prefix.
+ + 1; // Reserve space for the sign.
+}
+
+template <unsigned_integral _Tp, size_t _Base>
+_LIBCPP_HIDE_FROM_ABI constexpr size_t __buffer_size() noexcept
+ requires(_Base == 10) {
+ return numeric_limits<_Tp>::digits10 // The floored value.
+ + 1 // Turn floor to ceil.
+ + 1; // Reserve space for the sign.
+}
+
+template <unsigned_integral _Tp, size_t _Base>
+_LIBCPP_HIDE_FROM_ABI constexpr size_t __buffer_size() noexcept
+ requires(_Base == 16) {
+ return numeric_limits<_Tp>::digits // The number of binary digits.
+ / 4 // Adjust to hexadecimal.
+ + 2 // Reserve space for the '0[Xx]' prefix.
+ + 1; // Reserve space for the sign.
+}
+
+_LIBCPP_HIDE_FROM_ABI inline char* __insert_sign(char* __buf, bool __negative,
+ _Flags::_Sign __sign) {
+ if (__negative)
+ *__buf++ = '-';
+ else
+ switch (__sign) {
+ case _Flags::_Sign::__default:
+ case _Flags::_Sign::__minus:
+ // No sign added.
+ break;
+ case _Flags::_Sign::__plus:
+ *__buf++ = '+';
+ break;
+ case _Flags::_Sign::__space:
+ *__buf++ = ' ';
+ break;
+ }
+
+ return __buf;
+}
+
+_LIBCPP_HIDE_FROM_ABI constexpr char __hex_to_upper(char c) {
+ switch (c) {
+ case 'a':
+ return 'A';
+ case 'b':
+ return 'B';
+ case 'c':
+ return 'C';
+ case 'd':
+ return 'D';
+ case 'e':
+ return 'E';
+ case 'f':
+ return 'F';
+ }
+ return c;
+}
+
+/**
+ * Determines the required grouping based on the size of the input.
+ *
+ * The grouping's last element will be repeated. For simplicity this repeating
+ * is unwrapped based on the length of the input. (When the input is short some
+ * groups are not processed.)
+ *
+ * @returns The size of the groups to write. This means the number of
+ * separator characters written is size() - 1.
+ *
+ * @note Since zero-sized groups cause issues they are silently ignored.
+ *
+ * @note The grouping field of the locale is always a @c std::string,
+ * regardless whether the @c std::numpunct's type is @c char or @c wchar_t.
+ */
+_LIBCPP_HIDE_FROM_ABI inline string
+__determine_grouping(ptrdiff_t __size, const string& __grouping) {
+ _LIBCPP_ASSERT(!__grouping.empty() && __size > __grouping[0],
+ "The slow grouping formatting is used while there will be no "
+ "separators written");
+ string __r;
+ auto __end = __grouping.end() - 1;
+ auto __ptr = __grouping.begin();
+
+ while (true) {
+ __size -= *__ptr;
+ if (__size > 0)
+ __r.push_back(*__ptr);
+ else {
+ // __size <= 0 so the value pushed will be <= *__ptr.
+ __r.push_back(*__ptr + __size);
+ return __r;
+ }
+
+ // Proceed to the next group.
+ if (__ptr != __end) {
+ do {
+ ++__ptr;
+ // Skip grouping with a width of 0.
+ } while (*__ptr == 0 && __ptr != __end);
+ }
+ }
+
+ _LIBCPP_UNREACHABLE();
+}
+
+template <class _Parser>
+requires __formatter::__char_type<typename _Parser::char_type>
+class _LIBCPP_TEMPLATE_VIS __formatter_integral : public _Parser {
+public:
+ using _CharT = typename _Parser::char_type;
+
+ template <integral _Tp>
+ _LIBCPP_HIDE_FROM_ABI auto format(_Tp __value, auto& __ctx)
+ -> decltype(__ctx.out()) {
+ if (this->__width_needs_substitution())
+ this->__substitute_width_arg_id(__ctx.arg(this->__width));
+
+ if (this->__type == _Flags::_Type::__char)
+ return __format_as_char(__value, __ctx);
+
+ if constexpr (unsigned_integral<_Tp>)
+ return __format_unsigned_integral(__value, false, __ctx);
+ else {
+ // Depending on the std-format-spec string the sign and the value
+ // might not be outputted together:
+ // - alternate form may insert a prefix string.
+ // - zero-padding may insert additional '0' characters.
+ // Therefore the value is processed as a positive unsigned value.
+ // The function @ref __insert_sign will a '-' when the value was negative.
+ auto __r = __to_unsigned_like(__value);
+ bool __negative = __value < 0;
+ if (__negative)
+ __r = __complement(__r);
+
+ return __format_unsigned_integral(__r, __negative, __ctx);
+ }
+ }
+
+private:
+ /** Generic formatting for format-type c. */
+ _LIBCPP_HIDE_FROM_ABI auto __format_as_char(integral auto __value,
+ auto& __ctx)
+ -> decltype(__ctx.out()) {
+ if (this->__alignment == _Flags::_Alignment::__default)
+ this->__alignment = _Flags::_Alignment::__right;
+
+ using _Tp = decltype(__value);
+ if constexpr (!same_as<_CharT, _Tp>) {
+ // cmp_less and cmp_greater can't be used for character types.
+ if constexpr (signed_integral<_CharT> == signed_integral<_Tp>) {
+ if (__value < numeric_limits<_CharT>::min() ||
+ __value > numeric_limits<_CharT>::max())
+ __throw_format_error(
+ "Integral value outside the range of the char type");
+ } else if constexpr (signed_integral<_CharT>) {
+ // _CharT is signed _Tp is unsigned
+ if (__value >
+ static_cast<make_unsigned_t<_CharT>>(numeric_limits<_CharT>::max()))
+ __throw_format_error(
+ "Integral value outside the range of the char type");
+ } else {
+ // _CharT is unsigned _Tp is signed
+ if (__value < 0 || static_cast<make_unsigned_t<_Tp>>(__value) >
+ numeric_limits<_CharT>::max())
+ __throw_format_error(
+ "Integral value outside the range of the char type");
+ }
+ }
+
+ const auto __c = static_cast<_CharT>(__value);
+ return __write(_VSTD::addressof(__c), _VSTD::addressof(__c) + 1,
+ __ctx.out());
+ }
+
+ /**
+ * Generic formatting for format-type bBdoxX.
+ *
+ * This small wrapper allocates a buffer with the required size. Then calls
+ * the real formatter with the buffer and the prefix for the base.
+ */
+ _LIBCPP_HIDE_FROM_ABI auto
+ __format_unsigned_integral(unsigned_integral auto __value, bool __negative,
+ auto& __ctx) -> decltype(__ctx.out()) {
+ switch (this->__type) {
+ case _Flags::_Type::__binary_lower_case: {
+ array<char, __buffer_size<decltype(__value), 2>()> __array;
+ return __format_unsigned_integral(__array.begin(), __array.end(), __value,
+ __negative, 2, __ctx, "0b");
+ }
+ case _Flags::_Type::__binary_upper_case: {
+ array<char, __buffer_size<decltype(__value), 2>()> __array;
+ return __format_unsigned_integral(__array.begin(), __array.end(), __value,
+ __negative, 2, __ctx, "0B");
+ }
+ case _Flags::_Type::__octal: {
+ // Octal is special; if __value == 0 there's no prefix.
+ array<char, __buffer_size<decltype(__value), 8>()> __array;
+ return __format_unsigned_integral(__array.begin(), __array.end(), __value,
+ __negative, 8, __ctx,
+ __value != 0 ? "0" : nullptr);
+ }
+ case _Flags::_Type::__decimal: {
+ array<char, __buffer_size<decltype(__value), 10>()> __array;
+ return __format_unsigned_integral(__array.begin(), __array.end(), __value,
+ __negative, 10, __ctx, nullptr);
+ }
+ case _Flags::_Type::__hexadecimal_lower_case: {
+ array<char, __buffer_size<decltype(__value), 16>()> __array;
+ return __format_unsigned_integral(__array.begin(), __array.end(), __value,
+ __negative, 16, __ctx, "0x");
+ }
+ case _Flags::_Type::__hexadecimal_upper_case: {
+ array<char, __buffer_size<decltype(__value), 16>()> __array;
+ return __format_unsigned_integral(__array.begin(), __array.end(), __value,
+ __negative, 16, __ctx, "0X");
+ }
+ default:
+ _LIBCPP_ASSERT(false, "The parser should have validated the type");
+ _LIBCPP_UNREACHABLE();
+ }
+ }
+
+ template <class _Tp>
+ requires(same_as<char, _Tp> || same_as<wchar_t, _Tp>) _LIBCPP_HIDE_FROM_ABI
+ auto __write(const _Tp* __first, const _Tp* __last, auto __out_it)
+ -> decltype(__out_it) {
+
+ unsigned __size = __last - __first;
+ if (this->__type != _Flags::_Type::__hexadecimal_upper_case) [[likely]] {
+ if (__size >= this->__width)
+ return _VSTD::copy(__first, __last, _VSTD::move(__out_it));
+
+ return __formatter::__write(_VSTD::move(__out_it), __first, __last,
+ __size, this->__width, this->__fill,
+ this->__alignment);
+ }
+
+ // this->__type == _Flags::_Type::__hexadecimal_upper_case
+ // This means all characters in the range [a-f] need to be changed to their
+ // uppercase representation. The transformation is done as transformation
+ // in the output routine instead of before. This avoids another pass over
+ // the data.
+ // TODO FMT See whether it's possible to do this transformation during the
+ // conversion. (This probably requires changing std::to_chars' alphabet.)
+ if (__size >= this->__width)
+ return _VSTD::transform(__first, __last, _VSTD::move(__out_it),
+ __hex_to_upper);
+
+ return __formatter::__write(_VSTD::move(__out_it), __first, __last, __size,
+ __hex_to_upper, this->__width, this->__fill,
+ this->__alignment);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI auto
+ __format_unsigned_integral(char* __begin, char* __end,
+ unsigned_integral auto __value, bool __negative,
+ int __base, auto& __ctx, const char* __prefix)
+ -> decltype(__ctx.out()) {
+ char* __first = __insert_sign(__begin, __negative, this->__sign);
+ if (this->__alternate_form && __prefix)
+ while (*__prefix)
+ *__first++ = *__prefix++;
+
+ char* __last = __to_buffer(__first, __end, __value, __base);
+#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+ if (this->__locale_specific_form) {
+ const auto& __np = use_facet<numpunct<_CharT>>(__ctx.locale());
+ string __grouping = __np.grouping();
+ ptrdiff_t __size = __last - __first;
+ // Writing the grouped form has more overhead than the normal output
+ // routines. If there will be no separators written the locale-specific
+ // form is identical to the normal routine. Test whether to grouped form
+ // is required.
+ if (!__grouping.empty() && __size > __grouping[0])
+ return __format_grouping(__ctx.out(), __begin, __first, __last,
+ __determine_grouping(__size, __grouping),
+ __np.thousands_sep());
+ }
+#endif
+ auto __out_it = __ctx.out();
+ if (this->__alignment != _Flags::_Alignment::__default)
+ __first = __begin;
+ else {
+ // __buf contains [sign][prefix]data
+ // ^ location of __first
+ // The zero padding is done like:
+ // - Write [sign][prefix]
+ // - Write data right aligned with '0' as fill character.
+ __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it));
+ this->__alignment = _Flags::_Alignment::__right;
+ this->__fill = _CharT('0');
+ unsigned __size = __first - __begin;
+ this->__width -= _VSTD::min(__size, this->__width);
+ }
+
+ return __write(__first, __last, _VSTD::move(__out_it));
+ }
+
+#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+ /** Format's the locale-specific form's groupings. */
+ template <class _OutIt, class _CharT>
+ _LIBCPP_HIDE_FROM_ABI _OutIt
+ __format_grouping(_OutIt __out_it, const char* __begin, const char* __first,
+ const char* __last, string&& __grouping, _CharT __sep) {
+
+ // TODO FMT This function duplicates some functionality of the normal
+ // output routines. Evaluate whether these parts can be efficiently
+ // combined with the existing routines.
+
+ unsigned __size = (__first - __begin) + // [sign][prefix]
+ (__last - __first) + // data
+ (__grouping.size() - 1); // number of separator characters
+
+ __formatter::__padding_size_result __padding = {0, 0};
+ if (this->__alignment == _Flags::_Alignment::__default) {
+ // Write [sign][prefix].
+ __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it));
+
+ if (this->__width > __size) {
+ // Write zero padding.
+ __padding.__before = this->__width - __size;
+ __out_it = _VSTD::fill_n(_VSTD::move(__out_it), this->__width - __size,
+ _CharT('0'));
+ }
+ } else {
+ if (this->__width > __size) {
+ // Determine padding and write padding.
+ __padding = __formatter::__padding_size(__size, this->__width,
+ this->__alignment);
+
+ __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before,
+ this->__fill);
+ }
+ // Write [sign][prefix].
+ __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it));
+ }
+
+ auto __r = __grouping.rbegin();
+ auto __e = __grouping.rend() - 1;
+ _LIBCPP_ASSERT(__r != __e, "The slow grouping formatting is used while "
+ "there will be no separators written.");
+ // The output is divided in small groups of numbers to write:
+ // - A group before the first separator.
+ // - A separator and a group, repeated for the number of separators.
+ // - A group after the last separator.
+ // This loop achieves that process by testing the termination condition
+ // midway in the loop.
+ //
+ // TODO FMT This loop evaluates the loop invariant `this->__type !=
+ // _Flags::_Type::__hexadecimal_upper_case` for every iteration. (This test
+ // happens in the __write call.) Benchmark whether making two loops and
+ // hoisting the invariant is worth the effort.
+ while (true) {
+ if (this->__type == _Flags::_Type::__hexadecimal_upper_case) {
+ __last = __first + *__r;
+ __out_it = _VSTD::transform(__first, __last, _VSTD::move(__out_it),
+ __hex_to_upper);
+ __first = __last;
+ } else {
+ __out_it = _VSTD::copy_n(__first, *__r, _VSTD::move(__out_it));
+ __first += *__r;
+ }
+
+ if (__r == __e)
+ break;
+
+ ++__r;
+ *__out_it++ = __sep;
+ }
+
+ return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after,
+ this->__fill);
+ }
+#endif // _LIBCPP_HAS_NO_LOCALIZATION
+};
+
+} // namespace __format_spec
+
+#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+#endif //_LIBCPP_STD_VER > 17
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___FORMAT_FORMATTER_INTEGRAL_H
diff --git a/include/__format/parser_std_format_spec.h b/include/__format/parser_std_format_spec.h
index 3d9d7c7..77ee380 100644
--- a/include/__format/parser_std_format_spec.h
+++ b/include/__format/parser_std_format_spec.h
@@ -606,9 +606,7 @@
public:
using char_type = _CharT;
- // TODO FMT This class probably doesn't need public member functions after
- // format.string.std/std_format_spec_integral.pass.cpp has been retired.
-
+protected:
/**
* The low-level std-format-spec parse function.
*
@@ -619,7 +617,7 @@
*
* @returns The iterator pointing at the last parsed character.
*/
- _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx)
+ _LIBCPP_HIDE_FROM_ABI constexpr auto __parse(auto& __parse_ctx)
-> decltype(__parse_ctx.begin()) {
auto __begin = __parse_ctx.begin();
auto __end = __parse_ctx.end();
@@ -661,7 +659,6 @@
return __begin;
}
-protected:
/**
* Handles the post-parsing updates for the integer types.
*
diff --git a/include/format b/include/format
index a869bbe..6e779fe 100644
--- a/include/format
+++ b/include/format
@@ -275,9 +275,11 @@
#include <__format/format_args.h>
#include <__format/format_context.h>
#include <__format/format_error.h>
+#include <__format/format_fwd.h>
#include <__format/format_parse_context.h>
#include <__format/format_string.h>
#include <__format/formatter.h>
+#include <__format/formatter_integer.h>
#include <__format/formatter_string.h>
#include <__format/parser_std_format_spec.h>
#include <__variant/monostate.h>
@@ -385,9 +387,6 @@
template <class _Uv>
_LIBCPP_HIDDEN auto __handle_format(_Uv __value, auto& __ctx)
-> decltype(__ctx.out())
-#ifndef _LIBCPP_HAS_NO_INT128
- requires(!same_as<_Uv, __int128_t> && !same_as<_Uv, __uint128_t>)
-#endif
{
// TODO FMT Implement using formatting arguments
// TODO FMT Improve PoC since using std::to_string is inefficient.
@@ -400,20 +399,6 @@
*__out_it++ = __str[__i];
return __out_it;
}
-#ifndef _LIBCPP_HAS_NO_INT128
- template <class _Uv>
- _LIBCPP_HIDDEN auto __handle_format(_Uv __value, auto& __ctx)
- -> decltype(__ctx.out()) requires(same_as<_Uv, __int128_t> ||
- same_as<_Uv, __uint128_t>) {
- using _To = conditional_t<is_signed_v<_Uv>, long long, unsigned long long>;
- // TODO FMT Implement full 128-bit support.
- if (__value < numeric_limits<_To>::min() ||
- __value > numeric_limits<_To>::max())
- __throw_format_error("128-bit value is outside of implemented range");
-
- return __handle_format(static_cast<_To>(__value), __ctx);
- }
-#endif
};
} // namespace __format
@@ -456,50 +441,6 @@
}
};
-// Signed integral types.
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<signed char, _CharT>
- : public __format::__formatter_arithmetic<signed char, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<short, _CharT>
- : public __format::__formatter_arithmetic<short, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<int, _CharT>
- : public __format::__formatter_arithmetic<int, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<long, _CharT>
- : public __format::__formatter_arithmetic<long, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<long long, _CharT>
- : public __format::__formatter_arithmetic<long long, _CharT> {};
-#ifndef _LIBCPP_HAS_NO_INT128
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<__int128_t, _CharT>
- : public __format::__formatter_arithmetic<__int128_t, _CharT> {};
-#endif
-
-// Unsigned integral types.
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<unsigned char, _CharT>
- : public __format::__formatter_arithmetic<unsigned char, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<unsigned short, _CharT>
- : public __format::__formatter_arithmetic<unsigned short, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<unsigned, _CharT>
- : public __format::__formatter_arithmetic<unsigned, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<unsigned long, _CharT>
- : public __format::__formatter_arithmetic<unsigned long, _CharT> {};
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<unsigned long long, _CharT>
- : public __format::__formatter_arithmetic<unsigned long long, _CharT> {};
-#ifndef _LIBCPP_HAS_NO_INT128
-template <class _CharT>
-struct _LIBCPP_TEMPLATE_VIS formatter<__uint128_t, _CharT>
- : public __format::__formatter_arithmetic<__uint128_t, _CharT> {};
-#endif
-
// Floating point types.
// TODO FMT There are no replacements for the floating point stubs due to not
// having floating point support in std::to_chars yet. These stubs aren't
diff --git a/include/module.modulemap b/include/module.modulemap
index 85b093b..f7c3058 100644
--- a/include/module.modulemap
+++ b/include/module.modulemap
@@ -450,6 +450,8 @@
module format_parse_context { private header "__format/format_parse_context.h" }
module format_string { private header "__format/format_string.h" }
module formatter { private header "__format/formatter.h" }
+ module formatter_integer { private header "__format/formatter_integer.h" }
+ module formatter_integral { private header "__format/formatter_integral.h" }
module formatter_string { private header "__format/formatter_string.h" }
module parser_std_format_spec { private header "__format/parser_std_format_spec.h" }
}