[PATCH v2] libstdc++: Store basic_format_arg::handle in __format::_Arg_value
Jonathan Wakely
jwakely@redhat.com
Thu Mar 5 09:50:15 GMT 2026
On Thu, 05 Mar 2026 at 07:07 +0100, Tomasz Kamiński wrote:
>This patch changes the type of _M_handle member of __format::_Arg_value
>from __format::_HandleBase union member to 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
"To avoid a cyclic dependency"
>instantiating _Arg_value<_Context> for its _M_val member, that in turn
>requires basic_format_arg<_Context>::handle, we define handle as nested
>class inside _Arg_value and change basic_format_arg<_Context>::handle
>to alias for it.
>
>Finally, the handle(_Tp&) constructor is now constrained with to not accept
Strike "with" from the sentence above.
>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 (__Arg_value::handle): Define, extracted
> with modification from basic_format_arg::handle.
> (_Arg_value::_Handle_base): Remove.
> (_Arg_value::_M_handle): Change type to handle.
> (_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
> _Arg_value::handle.
>
>Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
>---
>v2:
>- makes handle a nested class inside _Arg_value
>- add new line after defitnion of basic_format_arg::handle alias
>The first paragraph already mentioned that storing exactly handle
>is required for placement new to work at compile time, so haven't
>expanded it.
>
>Testing on x86_64-linux. All *format* test passed.
>OK for trunk when test passes?
I like this version more than introducing the new _Handle class
template at namespace scope, thanks.
OK for trunk with the grammar tweaks noted above (no need to resend
the patch for that change, just edit the commit message and push).
> libstdc++-v3/include/std/format | 110 +++++++++++++++-----------------
> 1 file changed, 50 insertions(+), 60 deletions(-)
>
>diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
>index 2e4463c6596..b014936a21e 100644
>--- a/libstdc++-v3/include/std/format
>+++ b/libstdc++-v3/include/std/format
>@@ -4113,10 +4113,52 @@ namespace __format
> {
> using _CharT = typename _Context::char_type;
>
>- struct _HandleBase
>+ class handle
> {
>+ using _CharT = typename _Context::char_type;
>+ using _Func = void(*)(basic_format_parse_context<_CharT>&,
>+ _Context&, const void*);
>+
>+ // 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:
>+ 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;
>- void (*_M_func)();
>+ _Func _M_func;
> };
>
> union
>@@ -4142,7 +4184,7 @@ namespace __format
> const _CharT* _M_str;
> basic_string_view<_CharT> _M_sv;
> const void* _M_ptr;
>- _HandleBase _M_handle;
>+ handle _M_handle;
> #ifdef __SIZEOF_INT128__
> __int128 _M_i128;
> unsigned __int128 _M_u128;
>@@ -4232,8 +4274,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>)
>+ return __u._M_handle;
> // Otherwise, ill-formed.
> }
>
>@@ -4254,7 +4296,7 @@ namespace __format
> void
> _M_set(_Tp __v) noexcept
> {
>- if constexpr (derived_from<_Tp, _HandleBase>)
>+ if constexpr (is_same_v<_Tp, handle>)
> std::construct_at(&_M_handle, __v);
> else
> _S_get<_Tp>(*this) = __v;
>@@ -4279,57 +4321,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::_Arg_value<_Context>::handle;
>
> [[__gnu__::__always_inline__]]
> basic_format_arg() noexcept : _M_type(__format::_Arg_none) { }
>@@ -4623,10 +4616,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