On Tue, Feb 24, 2026 at 10:51 AM Tomasz Kamiński <[email protected]> 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 <[email protected]> > --- > 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. > All tests passed. > > 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)(); > - }; > + // 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 > + > + _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>; > [[__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 > >
