On Thu, Jun 25, 2026 at 10:13 AM Tomasz Kaminski <[email protected]> wrote:
> > > On Thu, Jun 25, 2026 at 10:05 AM Tomasz Kamiński <[email protected]> > wrote: > >> The LWG4515, "format: a and A should insert the 0x or 0X prefix", >> points that format currently does not provide ability to emit >> prefixed hexadecmial 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 thye 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. >> --- >> The second PoC path in series, adds 'p'/'P' support for both integers and >> floating point types, showing that the implementation is correct and >> sufficient. >> >> Tested on x86_64-linux locally. Full test incomming. *format* test passed >> locally. >> > OK for trunk/GCC-16 when all test passes? > All tests passed. > >> >> libstdc++-v3/include/std/format | 100 +++++++++++++++++--------------- >> 1 file changed, 54 insertions(+), 46 deletions(-) >> >> diff --git a/libstdc++-v3/include/std/format >> b/libstdc++-v3/include/std/format >> index 872a86c76a4..a469ead08b4 100644 >> --- a/libstdc++-v3/include/std/format >> +++ b/libstdc++-v3/include/std/format >> @@ -494,9 +494,7 @@ namespace __format >> // Presentation types for integral types (including bool and charT). >> _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_g = 1, _Pres_G, _Pres_a, _Pres_A, _Pres_e, _Pres_E, _Pres_f, >> _Pres_F, _Pres_p, _Pres_P, >> _Pres_max = 0xf, >> }; >> using enum _Pres_type; >> @@ -1751,7 +1749,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 +2125,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 +2181,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 +2201,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 +2232,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 +2270,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 +2294,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 +2310,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 +2374,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 +2395,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 +2411,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 +2459,11 @@ namespace __format >> __e = __str.size(); >> const size_t __r = __str.size() - __e; // Length of remainder. >> auto __overwrite = [&](_CharT* __p, size_t) { >> + 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 +2494,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 +2545,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 +2578,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; >> } >> } >> >> -- >> 2.54.0 >> >>
