[PATCH 1/2] libstdc++: Reserve _Pres_type value for prefixed hexadecimal floating presentation

Jonathan Wakely jwakely@redhat.com
Fri Jul 3 12:40:05 GMT 2026


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



More information about the Libstdc++ mailing list