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