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

Reply via email to