On Thu, 25 Jun 2026 at 10:00 +0200, Tomasz Kamiński 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.
"they"
(__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.
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,
Please add a line break.
_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) {
I think I'd like a comment before the copy_n call saying something
like: copy any +/- sign and "0x" prefix
OK with those changes.
+ 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