Sending this proof of concept patch, to illustrate how I envision
_M_handle_unrecognized would be used to introduce new argument types
without introducing any breaking changes, at cost of additional
function call and indirection.

My initial test showed out that we indeed se benefit of skipping out
formatte call, for the enum that maps to unsigned char, I got:
-------------------------------------------------------
Benchmark             Time             CPU   Iterations
-------------------------------------------------------
BM_Formatter       35.0 ns         34.9 ns     20079718
BM_FormatAs        20.5 ns         20.5 ns     3413317

(Formatter use std::format path, FormatAs provides format_as overload
returning unsigned char).

---
The implementation introduces a new _Arg_t values: _Arg_hbool, _Arg_hc,
_Arg_hll, _Arg_hdbl, _Arg_hsv, that indicates that respective
type is stored directly in _Arg_value, however it is result of format_as
call, and thus should be mapped to handle when visited by user,
and not recognized by check_dynamic_spec. They are esentially handled
differently by _M_visit and _M_visit_user methods.

To indicate that this new enums should be used, the values are
mapped to _HandleWrapped<T>. This is only used for limited set of types
(to reduce _Arg_t duplicaiton), and incompatible format_as result
are simply mapped throu handle.

The mnapping is performed by newly add _S_format_as_to_type function.
For integer types and floating point types, we map smaller types
(int, float) to the respective largest type stored uncoditionally
(long long, double).

The _M_handle_wrapped is also updated, to recognize the new arg
types and store them as _M_handle.
---
 libstdc++-v3/include/std/format | 260 +++++++++++++++++++++++++++++---
 1 file changed, 242 insertions(+), 18 deletions(-)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index 0f1a295189d..d9fd49e16b9 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -4142,11 +4142,43 @@ namespace __format
       }
     };
 
+  template<typename _Tp>
+    void format_as() = delete;
+
+  template<typename _Tp>
+    using __format_as_type = 
remove_cvref_t<decltype(format_as(std::declval<_Tp>()))>;
+
+  template<typename _Tp, typename _Context>
+    concept __has_format_as
+      = __formattable_impl<__format_as_type<_Tp>, typename 
_Context::char_type, _Context>;
+
+  template<typename _Tp>
+    struct _HandleWrapped
+    {
+      using type = _HandleWrapped<_Tp>;
+      using value_type = _Tp;
+
+      _HandleWrapped() = default;
+
+      template<typename _Up>
+       _HandleWrapped(const _Up& __up)
+       : __value(format_as(__up))
+       { }
+
+      _Tp __value;
+    };
+
+  template<typename _Tp>
+    concept __handle_wrapped
+      = is_same_v<_Tp, _HandleWrapped<typename _Tp::value_type>>;
+
   enum class _Arg_t : unsigned char {
     _Arg_none, _Arg_bool, _Arg_c, _Arg_i, _Arg_u, _Arg_ll, _Arg_ull,
     _Arg_flt, _Arg_dbl, _Arg_ldbl, _Arg_str, _Arg_sv, _Arg_ptr, _Arg_handle,
     _Arg_i128, _Arg_u128, _Arg_float128,
     _Arg_bf16, _Arg_f16, _Arg_f32, _Arg_f64,
+    // format_as result, that should be wrapped in handle
+    _Arg_hbool, _Arg_hc, _Arg_hll, _Arg_hdbl, _Arg_hsv,
     _Arg_max_,
 
 #ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
@@ -4355,6 +4387,8 @@ namespace __format
            std::construct_at(&_M_sv, __v);
          else if constexpr (is_same_v<_Tp, handle>)
            std::construct_at(&_M_handle, __v);
+         else if constexpr (__handle_wrapped<_Tp>)
+           _M_set(__v.__value);
          else
            // Builtin types are trivially default constructible, and assignment
            // changes active member per N5032 [class.union.general] p5.
@@ -4416,6 +4450,71 @@ namespace __format
       __format::_Arg_value<_Context> _M_val;
       __format::_Arg_t _M_type;
 
+      // Transform incoming argument type to the type stored in _Arg_value.
+      // e.g. short -> int, std::string -> std::string_view,
+      // char[3] -> const char*.
+      template<typename _Tp>
+       static consteval auto
+       _S_format_as_to_type()
+       {
+         using __format::_HandleWrapped;
+         using _Ft = __format::__format_as_type<_Tp>;
+         if constexpr (is_same_v<_Ft, bool>)
+           return _HandleWrapped<bool>();
+         else if constexpr (is_same_v<_Ft, _CharT>)
+           return _HandleWrapped<_CharT>();
+         else if constexpr (is_same_v<_Ft, char> && is_same_v<_CharT, wchar_t>)
+           return _HandleWrapped<_CharT>();
+         else if constexpr (__is_signed_integer<_Ft>::value)
+           {
+             if constexpr (sizeof(_Ft) <= sizeof(long long))
+               return _HandleWrapped<long long>();
+             else
+               return type_identity<handle>();
+           }
+         else if constexpr (__is_unsigned_integer<_Ft>::value)
+           {
+             if constexpr (sizeof(_Ft) < sizeof(unsigned long long))
+               return _HandleWrapped<long long>();
+             else
+               return type_identity<handle>();
+           }
+         else if constexpr (is_same_v<_Ft, float>)
+           return _HandleWrapped<double>();
+         else if constexpr (is_same_v<_Ft, double>)
+           return _HandleWrapped<double>();
+#ifdef _GLIBCXX_DOUBLE_IS_IEEE_BINARY64
+# ifdef __STDCPP_BFLOAT16_T__
+         else if constexpr (is_same_v<_Ft, __format::__bflt16_t>)
+           return _HandleWrapped<double>();
+# endif
+# ifdef __STDCPP_FLOAT16_T__
+         else if constexpr (is_same_v<_Ft, _Float16>)
+           return _HandleWrapped<double>();
+# endif
+# ifdef __FLT32_DIG__
+         else if constexpr (is_same_v<_Ft, _Float32>)
+           return _HandleWrapped<double>();
+# endif
+# ifdef __FLT64_DIG__
+         else if constexpr (is_same_v<_Ft, _Float64>)
+           return _HandleWrapped<double>();
+# endif
+#endif
+         else if constexpr (__is_specialization_of<_Ft, basic_string_view>
+                           || __is_specialization_of<_Ft, basic_string>)
+           {
+             if constexpr (is_same_v<typename _Ft::value_type, _CharT>)
+               return _HandleWrapped<basic_string_view<_CharT>>();
+             else
+               return type_identity<handle>();
+           }
+         else if constexpr (is_same_v<decay_t<_Ft>, const _CharT*>)
+           return _HandleWrapped<basic_string_view<_CharT>>();
+         else
+           return type_identity<handle>();
+       }
+
       // Transform incoming argument type to the type stored in _Arg_value.
       // e.g. short -> int, std::string -> std::string_view,
       // char[3] -> const char*.
@@ -4499,6 +4598,8 @@ namespace __format
            return type_identity<const void*>();
          else if constexpr (is_same_v<_Td, nullptr_t>)
            return type_identity<const void*>();
+         else if constexpr (__format::__has_format_as<_Tp, _Context>)
+           return _S_format_as_to_type<_Tp>();
          else
            return type_identity<handle>();
        }
@@ -4571,6 +4672,16 @@ namespace __format
          else if constexpr (is_same_v<_Tp, unsigned __int128>)
            return _Arg_u128;
 #endif
+         else if constexpr (is_same_v<_Tp, _HandleWrapped<bool>>)
+           return _Arg_hbool;
+         else if constexpr (is_same_v<_Tp, _HandleWrapped<_CharT>>)
+           return _Arg_hc;
+         else if constexpr (is_same_v<_Tp, _HandleWrapped<long long>>)
+           return _Arg_hll;
+         else if constexpr (is_same_v<_Tp, _HandleWrapped<double>>)
+           return _Arg_hdbl;
+         else if constexpr (is_same_v<_Tp, 
_HandleWrapped<basic_string_view<_CharT>>>)
+           return _Arg_hsv;
          else if constexpr (is_same_v<_Tp, handle>)
            return _Arg_handle;
        }
@@ -4620,7 +4731,21 @@ namespace __format
        // If new value of _Arg_t is introduced after GCC16, this
        // method should _M_type for it's value and return handle
        // referencing corresponding alternative in _M_arg_value.
-       __throw_format_error("format error: unrecognized argument type");
+       switch (_M_type)
+       {
+         using enum __format::_Arg_t;
+         case _Arg_hbool:
+           return handle(_M_val._M_bool);
+         case _Arg_hc:
+           return handle(_M_val._M_c);
+         case _Arg_hll:
+           return handle(_M_val._M_ll);
+         case _Arg_hdbl:
+           return handle(_M_val._M_dbl);
+         case _Arg_hsv:
+           return handle(_M_val._M_sv);
+       }
+       __throw_format_error("format error: unrecognized argument type");
       }
 
       template<typename _Visitor>
@@ -4633,14 +4758,17 @@ namespace __format
            case _Arg_none:
              return std::forward<_Visitor>(__vis)(_M_val._M_none);
            case _Arg_bool:
+           case _Arg_hbool:
              return std::forward<_Visitor>(__vis)(_M_val._M_bool);
            case _Arg_c:
+           case _Arg_hc:
              return std::forward<_Visitor>(__vis)(_M_val._M_c);
            case _Arg_i:
              return std::forward<_Visitor>(__vis)(_M_val._M_i);
            case _Arg_u:
              return std::forward<_Visitor>(__vis)(_M_val._M_u);
            case _Arg_ll:
+           case _Arg_hll:
              return std::forward<_Visitor>(__vis)(_M_val._M_ll);
            case _Arg_ull:
              return std::forward<_Visitor>(__vis)(_M_val._M_ull);
@@ -4648,6 +4776,7 @@ namespace __format
            case _Arg_flt:
              return std::forward<_Visitor>(__vis)(_M_val._M_flt);
            case _Arg_dbl:
+           case _Arg_hdbl:
              return std::forward<_Visitor>(__vis)(_M_val._M_dbl);
 #ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
            case _Arg_ldbl:
@@ -4682,6 +4811,7 @@ namespace __format
            case _Arg_str:
              return std::forward<_Visitor>(__vis)(_M_val._M_str);
            case _Arg_sv:
+           case _Arg_hsv:
              return std::forward<_Visitor>(__vis)(_M_val._M_sv);
            case _Arg_ptr:
              return std::forward<_Visitor>(__vis)(_M_val._M_ptr);
@@ -4706,23 +4836,99 @@ namespace __format
        _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
        _M_visit_user(_Visitor&& __vis)
        {
-         return _M_visit([&__vis]<typename _Tp>(_Tp& __val) -> decltype(auto)
-           {
-             constexpr bool __user_facing = __is_one_of<_Tp,
-               monostate, bool, _CharT,
-               int, unsigned int, long long int, unsigned long long int,
-               float, double, long double,
-               const _CharT*, basic_string_view<_CharT>,
-               const void*, handle>::value;
-            if constexpr (__user_facing)
-              return std::forward<_Visitor>(__vis)(__val);
-            else
-              {
-                handle __h(__val);
-                return std::forward<_Visitor>(__vis)(__h);
-              }
-         });
-       }
+         auto __as_handle = [&__vis](handle __h) -> decltype(auto)
+         { return std::forward<_Visitor>(__vis)(__h); };
+
+         switch (_M_type)
+         {
+           using enum __format::_Arg_t;
+           case _Arg_none:
+             return std::forward<_Visitor>(__vis)(_M_val._M_none);
+           case _Arg_bool:
+             return std::forward<_Visitor>(__vis)(_M_val._M_bool);
+           case _Arg_hbool:
+             return __as_handle(_M_val._M_bool);
+           case _Arg_c:
+             return std::forward<_Visitor>(__vis)(_M_val._M_c);
+           case _Arg_hc:
+             return __as_handle(_M_val._M_bool);
+           case _Arg_i:
+             return std::forward<_Visitor>(__vis)(_M_val._M_i);
+           case _Arg_u:
+             return std::forward<_Visitor>(__vis)(_M_val._M_u);
+           case _Arg_ll:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ll);
+           case _Arg_hll:
+             return __as_handle(_M_val._ll);
+           case _Arg_ull:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ull);
+#if __glibcxx_to_chars // FIXME: need to be able to format these types!
+           case _Arg_flt:
+             return std::forward<_Visitor>(__vis)(_M_val._M_flt);
+           case _Arg_dbl:
+             return std::forward<_Visitor>(__vis)(_M_val._M_dbl);
+           case _Arg_hdbl:
+             return __as_handle(_M_val._M_dbl);
+#ifndef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT
+           case _Arg_ldbl:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ldbl);
+#if defined(__SIZEOF_FLOAT128__) && _GLIBCXX_FORMAT_F128
+           case _Arg_float128:
+             return __as_handle(_M_val._M_float128);
+#endif
+#else
+           case _Arg_ibm128:
+             if constexpr (is_same_v<__ibm128, long double>)
+               return std::forward<_Visitor>(__vis)(_M_val._M_ibm128);
+             else
+               return __as_handle(_M_val._M_ibm128);
+           case _Arg_ieee128:
+             if constexpr (is_same_v<__ieee128, long double>)
+               return std::forward<_Visitor>(__vis)(_M_val._M_ieee128);
+             else
+               return __as_handle(_M_val._M_ieee128);
+#endif
+#if defined(__STDCPP_BFLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
+           case _Arg_bf16:
+             return __as_handle(_M_val._M_bf16);
+#endif
+#if defined(__STDCPP_FLOAT16_T__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
+           case _Arg_f16:
+             return __as_handle(_M_val._M_f16);
+#endif
+#if defined(__FLT32_DIG__) && defined(_GLIBCXX_FLOAT_IS_IEEE_BINARY32)
+           case _Arg_f32:
+             return __as_handle(_M_val._M_f32);
+#endif
+#if defined(__FLT64_DIG__) && defined(_GLIBCXX_DOUBLE_IS_IEEE_BINARY64)
+           case _Arg_f64:
+             return __as_handle(_M_val._M_f64);
+#endif
+#endif // __glibcxx_to_chars
+           case _Arg_str:
+             return std::forward<_Visitor>(__vis)(_M_val._M_str);
+           case _Arg_sv:
+             return std::forward<_Visitor>(__vis)(_M_val._M_sv);
+           case _Arg_hsv:
+             return __as_handle(_M_val._M_f64);
+           case _Arg_ptr:
+             return std::forward<_Visitor>(__vis)(_M_val._M_ptr);
+           case _Arg_handle:
+             return std::forward<_Visitor>(__vis)(_M_val._M_handle);
+#ifdef __SIZEOF_INT128__
+           case _Arg_i128:
+             return __as_handle(_M_val._M_i128);
+           case _Arg_u128:
+             return __as_handle(_M_val._M_u128);
+#endif
+           default:
+             // Call exported definition of _M_handle_unrecognized from 
libstdc++.so,
+             // that should recognize new _Arg_t values and return 
basic_format_arg,
+             // containing a handle to that value.
+             handle __h = _M_handle_unrecognized();
+             return std::forward<_Visitor>(__vis)(__h);
+       }
+      }
     };
 
   template<typename _Visitor, typename _Context>
@@ -5805,6 +6011,24 @@ namespace __format
     }
 #endif
 
+  template<class _Tp, class _CharT>
+    requires (is_enum_v<_Tp> || is_class_v<_Tp> || is_union_v<_Tp>)
+            && __format::__formattable_impl<__format::__format_as_type<_Tp>, 
_CharT>
+  struct formatter<_Tp, _CharT> {
+  public:
+    constexpr format_parse_context::iterator
+    parse(format_parse_context& __ctx)
+    { return _M_nested.parse(__ctx); }
+
+    template <typename _FormatContext>
+      typename _FormatContext::iterator
+      format(const _Tp& __val, _FormatContext& __ctx) const
+      { return _M_nested.format(__val, __ctx); }
+
+  private:
+    formatter<__format::__format_as_type<_Tp>, _CharT> _M_nested;
+  };
+
 #if __glibcxx_format_ranges // C++ >= 23 && HOSTED
   /// @cond undocumented
   template<typename _Tp>
-- 
2.54.0

Reply via email to