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 <[email protected]>
---
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



Reply via email to