https://gcc.gnu.org/g:ea402089660102b7eadc9b9c418295549d6aff2e
commit r17-2121-gea402089660102b7eadc9b9c418295549d6aff2e Author: Tomasz Kamiński <[email protected]> Date: Wed Jun 24 17:16:45 2026 +0200 libstdc++: Reserve _Pres_type value for prefixed hexadecimal floating presentation The LWG4515, "format: a and A should insert the 0x or 0X prefix", points that format currently does not provide ability to emit prefixed hexadecimal presentation for floating-point. While changing the output for a/A was not approved during Brno meeting, producing a printf equivalent output is desired functionality. This patch pre-emptively introduces and handles additional _Pres_type values for the purpose of expressing this implementation. The values are not currently user-facing (there is no corresponding format specifier), but adding them now will avoid problems caused by linking TU from older versions and allow the change to be handled as DR (if necessary). Currently _Pres_p/_Pres_P are used as placeholders for the values (matching their behavior for pointers), however they can renamed in future (only value is relevant). Note, that for __formatter_int, the behavior of P/p can be already expressed by setting _Pres_X/_Pres_x and _M_alt. In consequence the existing uses of this name (as aliases to X/x) in __formatter_ptr were adjusted accordingly. libstdc++-v3/ChangeLog: * include/std/format (_Pres_type::_Pres_p, _Pres_type::_Pres_P): Change the values to which they are defined. (__formatter_fp::format): Append 0x/0X if _M_type is _Pres_p/_Pres_P respectively. (__formatter_fp::_M_localize): Add __offset parameter representing start of number value (after sign and prefix). (__formatter_ptr::parse, __formatter_ptr::_M_default) (__formatter_ptr::__formatter_ptr): Remove unused __type parameter, and replace use of _Pres_p/P with _Pres_x/X. Reviewed-by: Jonathan Wakely <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> Diff: --- libstdc++-v3/include/std/format | 100 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format index 872a86c76a4a..dd275b6b8173 100644 --- a/libstdc++-v3/include/std/format +++ b/libstdc++-v3/include/std/format @@ -495,8 +495,7 @@ namespace __format _Pres_c = 2, _Pres_x, _Pres_X, _Pres_d, _Pres_o, _Pres_b, _Pres_B, // Presentation types for floating-point types _Pres_g = 1, _Pres_G, _Pres_a, _Pres_A, _Pres_e, _Pres_E, _Pres_f, _Pres_F, - // For pointers, the value are same as hexadecimal presentations for integers - _Pres_p = _Pres_x, _Pres_P = _Pres_X, + _Pres_p, _Pres_P, _Pres_max = 0xf, }; using enum _Pres_type; @@ -1751,7 +1750,6 @@ namespace __format } __start = __format::__put_sign(__i, _M_spec._M_sign, __start - 1); - string_view __narrow_str(__start, __res.ptr - __start); size_t __prefix_len = __start_digits - __start; if constexpr (is_same_v<char, _CharT>) @@ -2128,23 +2126,29 @@ namespace __format 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 = 'e'; + size_t __offset = 1; // reserve space for sign switch (_M_spec._M_type) { + case _Pres_P: + if (__builtin_isfinite(__v)) + __offset += 2; // reserve space for prefix + [[fallthrough]]; case _Pres_A: __upper = true; __expc = 'P'; + __fmt = chars_format::hex; + break; + case _Pres_p: + if (__builtin_isfinite(__v)) + __offset += 2; // reserve space for prefix [[fallthrough]]; case _Pres_a: - if (_M_spec._M_type != _Pres_A) - __expc = 'p'; + __expc = 'p'; __fmt = chars_format::hex; break; case _Pres_E: @@ -2178,6 +2182,9 @@ namespace __format break; } + char* __start = __buf + __offset; + char* __end = __buf + sizeof(__buf); + // Write value into buffer using std::to_chars. auto __to_chars = [&](char* __b, char* __e) { if (__use_prec) @@ -2195,7 +2202,7 @@ namespace __format { // 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 = 8 + __prec; + size_t __guess = 7 + __offset + __prec; if (__fmt == chars_format::fixed) // +ddd.prec { if constexpr (is_same_v<_Fp, float> || is_same_v<_Fp, double> @@ -2226,28 +2233,36 @@ namespace __format // instantiated with it, was fixed in ABI 18 (G++ 13). Since // <format> was new in G++ 13, and is experimental, that // isn't a problem. - auto __overwrite = [&__to_chars, &__res] (char* __p, size_t __n) + auto __overwrite = [&__to_chars, &__res, __offset] (char* __p, size_t __n) { - __res = __to_chars(__p + 1, __p + __n - 1); + __res = __to_chars(__p + __offset, __p + __n - __offset); return __res.ec == errc{} ? __res.ptr - __p : 0; }; __dynbuf.__resize_and_overwrite(__dynbuf.capacity() * 2, __overwrite); - __start = __dynbuf.data() + 1; // reserve space for sign + __start = __dynbuf.data() + __offset; // reserve space for sign and prefix __end = __dynbuf.data() + __dynbuf.size(); } while (__builtin_expect(__res.ec == errc::value_too_large, 0)); } - // Use uppercase for 'A', 'E', and 'G' formats. + if (__offset == 3) + { + __start -= 2; + if (__builtin_signbit(__v)) + ranges::copy(string_view("-0x"), __start); + else + ranges::copy(string_view("0x"), __start); + } + + // Use uppercase for 'A', 'P', 'E', and 'G' formats. if (__upper) { for (char* __p = __start; __p != __res.ptr; ++__p) *__p = __format::__toupper_numeric(*__p); } - bool __have_sign = true; // Add sign for non-negative values. if (!__builtin_signbit(__v)) { @@ -2256,7 +2271,7 @@ namespace __format else if (_M_spec._M_sign == _Sign_space) *--__start = ' '; else - __have_sign = false; + --__offset; } string_view __narrow_str(__start, __res.ptr - __start); @@ -2280,9 +2295,9 @@ namespace __format if (__trailing_zeros) { // Find number of digits after first significant figure. - if (__s[__have_sign] != '0') + if (__s[__offset] != '0') // A string like "D.D" or "-D.DDD" - __sigfigs = __p - __have_sign - 1; + __sigfigs = __p - __offset - 1; else // A string like "0.D" or "-0.0DD". // Safe to assume there is a non-zero digit, because @@ -2296,7 +2311,7 @@ namespace __format if (__p == __s.npos) __p = __s.size(); __d = __p; // Position where '.' should be inserted. - __sigfigs = __d - __have_sign; + __sigfigs = __d - __offset; } if (__trailing_zeros && __prec != 0) @@ -2360,7 +2375,7 @@ namespace __format if (_M_spec._M_localized && __builtin_isfinite(__v)) { - auto __s = _M_localize(__str, __expc, __fc.locale()); + auto __s = _M_localize(__str, __expc, __offset, __fc.locale()); if (!__s.empty()) __str = __wstr = std::move(__s); } @@ -2381,11 +2396,10 @@ namespace __format if (_M_spec._M_zero_fill && __builtin_isfinite(__v)) { __fill_char = _CharT('0'); - // Write sign before zero filling. - if (!__format::__is_xdigit(__narrow_str[0])) + if (__offset > 0) { - *__out++ = __str[0]; - __str.remove_prefix(1); + __out = __format::__write(__out, __str.substr(0, __offset)); + __str.remove_prefix(__offset); } } else @@ -2398,7 +2412,7 @@ namespace __format // Locale-specific format. basic_string<_CharT> _M_localize(basic_string_view<_CharT> __str, char __expc, - const locale& __loc) const + int __offset, const locale& __loc) const { basic_string<_CharT> __lstr; @@ -2446,16 +2460,12 @@ namespace __format __e = __str.size(); const size_t __r = __str.size() - __e; // Length of remainder. auto __overwrite = [&](_CharT* __p, size_t) { + // Copy any +/- sign and "0x" prefix + ranges::copy_n(__str.data(), __offset, __p); // Apply grouping to the digits before the radix or exponent. - int __off = 0; - if (auto __c = __str.front(); __c == '-' || __c == '+' || __c == ' ') - { - *__p = __c; - __off = 1; - } - auto __end = std::__add_grouping(__p + __off, __np.thousands_sep(), + auto __end = std::__add_grouping(__p + __offset, __np.thousands_sep(), __grp.data(), __grp.size(), - __str.data() + __off, + __str.data() + __offset, __str.data() + __e); if (__r) // If there's a fractional part or exponent { @@ -2486,25 +2496,25 @@ namespace __format __formatter_ptr() noexcept : _M_spec() { - _M_spec._M_type = _Pres_p; + _M_spec._M_type = _Pres_x; _M_spec._M_alt = true; } constexpr __formatter_ptr(_Spec<_CharT> __spec) noexcept : _M_spec(__spec) - { _M_set_default(_Pres_p); } + { _M_set_default(); } constexpr typename basic_format_parse_context<_CharT>::iterator - parse(basic_format_parse_context<_CharT>& __pc, _Pres_type __type = _Pres_p) + parse(basic_format_parse_context<_CharT>& __pc) { __format::_Spec<_CharT> __spec{}; const auto __last = __pc.end(); auto __first = __pc.begin(); - auto __finalize = [this, &__spec, __type] { + auto __finalize = [this, &__spec] { _M_spec = __spec; - _M_set_default(__type); + _M_set_default(); }; auto __finished = [&] { @@ -2537,15 +2547,15 @@ namespace __format if (*__first == 'p') { - __spec._M_type = _Pres_p; - __spec._M_alt = !__spec._M_alt; + __spec._M_type = _Pres_x; + __spec._M_alt = true; ++__first; } #if __glibcxx_format >= 202304L else if (*__first == 'P') { - __spec._M_type = _Pres_P; - __spec._M_alt = !__spec._M_alt; + __spec._M_type = _Pres_X; + __spec._M_alt = true; ++__first; } #endif @@ -2570,12 +2580,12 @@ namespace __format private: [[__gnu__::__always_inline__]] constexpr void - _M_set_default(_Pres_type __type) + _M_set_default() { - if (_M_spec._M_type == _Pres_none && __type != _Pres_none) + if (_M_spec._M_type == _Pres_none) { - _M_spec._M_type = __type; - _M_spec._M_alt = !_M_spec._M_alt; + _M_spec._M_type = _Pres_x; + _M_spec._M_alt = true; } }
