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
>>
>>

Reply via email to