[PATCH] libstdc++: Store basic_format_arg::handle in __format::_Arg_value

Jonathan Wakely jwakely@redhat.com
Wed Mar 4 14:54:43 GMT 2026


On Tue, 24 Feb 2026 at 10:47 +0100, Tomasz Kamiński wrote:
>This patch changes the type of _M_handle member of __format::_Arg_value
>from __format::_HandleBase union member basic_format_arg<_Context>::handle.
>This allows handle to be stored (using placement new) inside _Arg_value at
>compile time, as type _M_handle member now matches stored object.
>
>In addition to above, to make handle usable at compile time, we adjust
>the _M_func signature to match the stored function, avoiding the need
>for reinterpret cast.
>
>To avoid cycling dependency, where basic_format_arg<_Context> requires
>instantiating _Arg_value<_Context> for its _M_val member, that in turn
>requires basic_format_arg<_Context>::handle, we introduce __format::_Handle,
>and change basic_format_arg<_Context>::handle to alias for it. To make
>this unobservable we also define _Handle<_Context>::handle alias, to
>support uses of injected-class name,
>
>Finally, the _Handle(_Tp&) constructor is now constrained with to not accept
>_Handle itself, as otherwise it would be used instead of copy-constructor
>when constructing from _Handle&.
>
>As _Arg_value is already templated on _Context, this change should not lead
>to additional template instantiations.
>
>libstdc++-v3/ChangeLog:
>
>	* include/std/format (__format::_Handle): Define, extracted
>	with modification from basic_format_arg::handle.
>	(_Arg_value::_Handle_base): Remove.
>	(_Arg_value::_M_handle): Change type to _Handle<_Context>.
>	(_Arg_value::_M_get, _Arg_value::_M_set): Check for handle
>	type directly, and return result unmodified.
>	(basic_format_arg::__formattable): Remove.
>	(basic_format_arg::handle): Replace with alias to _Handle.
>
>Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
>---
>I do not see there is any drawback of doing the above, and this
>technically would be ABI break, but the actual binary representation
>does not change. So I think it would be coode to merge it to GCC-16.
>
>Testing on x86_64-linux. All *format* test already passed.
>OK for trunk when all test passes.
>
> libstdc++-v3/include/std/format | 122 +++++++++++++++-----------------
> 1 file changed, 57 insertions(+), 65 deletions(-)
>
>diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>index 2e4463c6596..66bf3c246e5 100644
>--- a/libstdc++-v3/include/std/format
>+++ b/libstdc++-v3/include/std/format
>@@ -4109,15 +4109,60 @@ namespace __format
>   using enum _Arg_t;
>
>   template<typename _Context>
>-    struct _Arg_value
>+    class _Handle
>     {
>       using _CharT = typename _Context::char_type;
>+      using _Func = void(*)(basic_format_parse_context<_CharT>&,
>+			    _Context&, const void*);
>
>-      struct _HandleBase
>-      {
>-	const void* _M_ptr;
>-	void (*_M_func)();

We could have just fixed the type of this member, right?

I stored it as void(*)() and used reinterpret_cast, but I think it
could always have been this type instead:
void(*)(basic_format_parse_context<_CharT>&, _Context&, const void*)

But I think it does still makes sense to get rid of the _HandleBase
type and just deal with one type (parameterized on Context) instead of
a base and a derived type.

>-      };
>+      // Format as const if possible, to reduce instantiations.
>+      template<typename _Tp>
>+	using __maybe_const_t
>+	  = __conditional_t<__formattable_with<const _Tp, _Context>,
>+			    const _Tp, _Tp>;
>+
>+      template<typename _Tq>
>+	static void
>+	_S_format(basic_format_parse_context<_CharT>& __parse_ctx,
>+		  _Context& __format_ctx, const void* __ptr)
>+	{
>+	  using _Td = remove_const_t<_Tq>;
>+	  typename _Context::template formatter_type<_Td> __f;
>+	  __parse_ctx.advance_to(__f.parse(__parse_ctx));
>+	  _Tq& __val = *const_cast<_Tq*>(static_cast<const _Td*>(__ptr));
>+	  __format_ctx.advance_to(__f.format(__val, __format_ctx));
>+	}
>+
>+      template<typename _Tp>
>+	requires (!is_same_v<remove_cv_t<_Tp>, _Handle>)
>+	explicit
>+	_Handle(_Tp& __val) noexcept
>+	: _M_ptr(__builtin_addressof(__val))
>+	, _M_func(&_S_format<__maybe_const_t<_Tp>>)
>+	{ }
>+
>+      friend class basic_format_arg<_Context>;
>+
>+    public:
>+      using handle = _Handle; // emulate injected class name

We could define _Arg_value<Context>::handle instead of a separate
_Handle<Context>, right? Then its injected class name would be
"handle" anyway.

i.e. still remove _Arg_value<C>::_HandleBase, but move
basic_format_arg<C>::handle to be defined as a nested class in
_Arg_value<C> instead. Would that be a little simpler?

>+
>+      _Handle(const _Handle&) = default;
>+      _Handle& operator=(const _Handle&) = default;
>+
>+      [[__gnu__::__always_inline__]]
>+      void
>+      format(basic_format_parse_context<_CharT>& __pc, _Context& __fc) const
>+      { _M_func(__pc, __fc, this->_M_ptr); }
>+
>+    private:
>+      const void* _M_ptr;
>+      _Func _M_func;
>+    };
>+
>+  template<typename _Context>
>+    struct _Arg_value
>+    {
>+      using _CharT = typename _Context::char_type;
>
>       union
>       {
>@@ -4142,7 +4187,7 @@ namespace __format
> 	const _CharT* _M_str;
> 	basic_string_view<_CharT> _M_sv;
> 	const void* _M_ptr;
>-	_HandleBase _M_handle;
>+	_Handle<_Context> _M_handle;
> #ifdef __SIZEOF_INT128__
> 	__int128 _M_i128;
> 	unsigned __int128 _M_u128;
>@@ -4232,8 +4277,8 @@ namespace __format
> 	  else if constexpr (is_same_v<_Tp, _Float64>)
> 	    return __u._M_f64;
> #endif
>-	  else if constexpr (derived_from<_Tp, _HandleBase>)
>-	    return static_cast<_Tp&>(__u._M_handle);
>+	  else if constexpr (is_same_v<_Tp, _Handle<_Context>>)
>+	    return __u._M_handle;
> 	  // Otherwise, ill-formed.
> 	}
>
>@@ -4254,7 +4299,7 @@ namespace __format
> 	void
> 	_M_set(_Tp __v) noexcept
> 	{
>-	  if constexpr (derived_from<_Tp, _HandleBase>)
>+	  if constexpr (is_same_v<_Tp, _Handle<_Context>>)
> 	    std::construct_at(&_M_handle, __v);
> 	  else
> 	    _S_get<_Tp>(*this) = __v;
>@@ -4279,58 +4324,8 @@ namespace __format
>     {
>       using _CharT = typename _Context::char_type;
>
>-      template<typename _Tp>
>-	static constexpr bool __formattable
>-	  = __format::__formattable_with<_Tp, _Context>;
>-
>     public:
>-      class handle : public __format::_Arg_value<_Context>::_HandleBase
>-      {
>-	using _Base = typename __format::_Arg_value<_Context>::_HandleBase;
>-
>-	// Format as const if possible, to reduce instantiations.
>-	template<typename _Tp>
>-	  using __maybe_const_t
>-	    = __conditional_t<__formattable<const _Tp>, const _Tp, _Tp>;
>-
>-	template<typename _Tq>
>-	  static void
>-	  _S_format(basic_format_parse_context<_CharT>& __parse_ctx,
>-		    _Context& __format_ctx, const void* __ptr)
>-	  {
>-	    using _Td = remove_const_t<_Tq>;
>-	    typename _Context::template formatter_type<_Td> __f;
>-	    __parse_ctx.advance_to(__f.parse(__parse_ctx));
>-	    _Tq& __val = *const_cast<_Tq*>(static_cast<const _Td*>(__ptr));
>-	    __format_ctx.advance_to(__f.format(__val, __format_ctx));
>-	  }
>-
>-	template<typename _Tp>
>-	  explicit
>-	  handle(_Tp& __val) noexcept
>-	  {
>-	    this->_M_ptr = __builtin_addressof(__val);
>-	    auto __func = _S_format<__maybe_const_t<_Tp>>;
>-	    this->_M_func = reinterpret_cast<void(*)()>(__func);
>-	  }
>-
>-	friend class basic_format_arg<_Context>;
>-
>-      public:
>-	handle(const handle&) = default;
>-	handle& operator=(const handle&) = default;
>-
>-	[[__gnu__::__always_inline__]]
>-	void
>-	format(basic_format_parse_context<_CharT>& __pc, _Context& __fc) const
>-	{
>-	  using _Func = void(*)(basic_format_parse_context<_CharT>&,
>-				_Context&, const void*);
>-	  auto __f = reinterpret_cast<_Func>(this->_M_func);
>-	  __f(__pc, __fc, this->_M_ptr);
>-	}
>-      };
>-
>+      using handle = __format::_Handle<_Context>;

Blank line between this using-declaration and the constructor please:

>       [[__gnu__::__always_inline__]]
>       basic_format_arg() noexcept : _M_type(__format::_Arg_none) { }
>
>@@ -4623,10 +4618,7 @@ namespace __format
> 	    case _Arg_ptr:
> 	      return std::forward<_Visitor>(__vis)(_M_val._M_ptr);
> 	    case _Arg_handle:
>-	    {
>-	      auto& __h = static_cast<handle&>(_M_val._M_handle);
>-	      return std::forward<_Visitor>(__vis)(__h);
>-	    }
>+	      return std::forward<_Visitor>(__vis)(_M_val._M_handle);
> #ifdef __SIZEOF_INT128__
> 	    case _Arg_i128:
> 	      return std::forward<_Visitor>(__vis)(_M_val._M_i128);
>-- 
>2.53.0
>
>



More information about the Libstdc++ mailing list