On Tue, Jul 7, 2026 at 11:00 AM Jonathan Wakely <[email protected]> wrote:

> On Wed, 24 Jun 2026 at 09:59 +0200, Tomasz Kamiński wrote:
> >Implementing features from later revision of the standard, may require
> >extending the set of types stored directly in _Arg_value (without
> >changing in size and aligment), and thus expanding the values in _Arg_t.
> >However, any such value would be unrecongnized by the TUs compiled with
>
> "unrecognized"
>
> >older release, and would lead to UB (call to __builtin_unreachable).
> >
> >This patch addresses above by introducing _M_handle_unrecognized method,
> >that is called instead. As specializations of this method for all
> >context supported at runtime (format_context and wformat_context) are
> >exported from libstdc++.so, the newest version (supporting all _Arg_t
> >values introduced later) will be picked.
>
> What happens if the compiler decides to inline the call to
> _M_handle_unrecognized() in the TU compiled with the old compiler?
>
> >In consequence, the
> >implementation may return handle object referencing new alternatvies
>
> "alternatives"
>
> >(such wrapping is already required to provide standard compliant
> >behavior of visit_format_arg).
> >As no new _Arg_t where introduced since GCC16, this method simply
>
> "where" -> "were"
>
> >throws format_error now, and contains appropariate comment.
> >
> >Note that the above is not required for formatting when __do_vformat_to
> >is exported (unicode literal encoding and C++20). It remains necessary
> >in remaining cases or when visit on basic_format_arg is called by user
> >or other. In consequence addition of new argument types stored directly
> >does not have negative impact on performance in most common case.
> >
> >This patch also removes the __type parameter from the _M_visit and
> >_M_visit_user, and uses _M_type member instead. This prevents misues,
> >as the provided type was always required to match _M_type.
> >
> >libstdc++-v3/ChangeLog:
> >
> >       * config/abi/pre/gnu.ver (GLIBCXX_3.4.36): Export basic_format_arg
> >       _M_handle_unrecognized method of specializations of
> >       format_context and wformat_context.
> >       * include/std/format (basic_format_arg::_M_handle_unrecognized):
> >       Define and declare explicit specializations for (w)format_context.
> >       (basic_format_arg::_M_visit): Remove __type parameter, and use
> >       _M_type instead. Call _M_handle_unrecognized for unrecognized
> >       _Arg_t values.
> >       (basic_format_arg::_M_visit_user): Remove __type parameter, and
> >       use _M_type instead. Adjust calls to _M_visit.
> >       (basic_format_arg::visit, std::visit_format_arg)
> >       (__format::__visit_format_arg): Remove _M_type argument from
> >       _M_visit(_user) calls.
> >       * src/c++20/format-inst.cc
> >       (basic_format_arg::_M_handle_unrecognized): Export explicit
> >       specializations for (w)format_context.
> >---
> >To test if this collectly handle the issue, I have tested this using
> >two version of GCC
> >* A16 - v16 version with this patch backported
> >* B17 - trunk with additional _Arg_t introduced to implement P3070,
> >       that are handled in _M_handle_unrecognized
> >        (see
> https://gcc.gnu.org/pipermail/libstdc++/2026-June/066849.html)
> >
> >Then I created a format.exe program composed with two TUs:
> >* create.cc that exposes function that format_args with new _Arg_t
> >  values, compiled with B17
> >* format.cc that visits and vformat's above, compiled with A16
> >/////////////////////
> >// create.cc compiled with B17
> >#include <format>
> >
> >enum class RGB : int { red, green, blue };
> >int format_as(RGB r) { return static_cast<int>(r); }
> >
> >std::format_args make_args()
> >{
> >  RGB r = RGB::red, g = RGB::green, b = RGB::blue;
> >  static auto args = std::make_format_args(r, g, b);
> >  return args;
> >}
> >///////////////////
> >// format.cc compiled with A16
> >#include <format>
> >#include <iostream>
> >
> >std::format_args make_args();
> >
> >int main() {
> >  std::visit_format_arg([]<typename T>(T t) {
> >   if constexpr (std::is_same_v<T,
> std::basic_format_arg<std::format_context>::handle>)
> >     std::cout << "Got handle" << std::endl;
> >  }, make_args().get(0));
> >  std::cout << std::vformat("{} {} {}", make_args()) << std::endl;
> >}
> >///////////////////
> >
> >Then invoking format.exe with libstdc++.so version gives following result
> >* A16: crashes due unhandled format_error exception
> >terminate called after throwing an instance of 'std::format_error'
> >  what():  format error: unrecognized argument type
> >Aborted                    (core dumped)
> LD_LIBRARY_PATH=/home/tkaminsk/gcc/16/lib64 ./format.exe
> >* B17: prints expected output
> >Got handle
> >0 1 2
> >
> >Tested on x86_64-linux. OK for trunk?
> >
> >Once merged, I will create separate patches for backporting,
> >as we need to 3.4.36 version to v16, and 3.4.37 on trunk for new symbols.
>
> If we're going to backport this to gcc-16 and move some symbols to
> GLIBCXX_3.4.37 for trunk, we should consider whether to backport the
> __recent_leap_second_info function too.
>
> > libstdc++-v3/config/abi/pre/gnu.ver   |  4 +++
> > libstdc++-v3/include/std/format       | 46 +++++++++++++++++++--------
> > libstdc++-v3/src/c++20/format-inst.cc |  9 ++++++
> > 3 files changed, 45 insertions(+), 14 deletions(-)
> >
> >diff --git a/libstdc++-v3/config/abi/pre/gnu.ver
> b/libstdc++-v3/config/abi/pre/gnu.ver
> >index 3a6afac8308..9e1bebb4f50 100644
> >--- a/libstdc++-v3/config/abi/pre/gnu.ver
> >+++ b/libstdc++-v3/config/abi/pre/gnu.ver
> >@@ -2628,6 +2628,10 @@ GLIBCXX_3.4.36 {
> >
> >
>  _ZNSt6chrono8__detail25__recent_leap_second_infoERNS_16leap_second_infoEj;
> >
> >+    # basic_format_arg<format_context>::handle
> basic_format_arg<format_context>::_M_handle_unrecognized() const;
> >+    # basic_format_arg<wformat_context>::handle
> basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
> >+
> _ZNKSt16basic_format_argISt20basic_format_contextINSt8__format10_Sink_iterI[cw]EE[cw]EE22_M_handle_unrecognizedEv;
> >+
> >     # basic_string::allocate_at_least
> >
>  _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_S_allocate_*;
> >
>  _ZNSt7__cxx1112basic_stringI[cw]St11char_traitsI[cw]ESaI[cw]EE*_M_create_*;
> >diff --git a/libstdc++-v3/include/std/format
> b/libstdc++-v3/include/std/format
> >index db226ecb0c8..3a083f68030 100644
> >--- a/libstdc++-v3/include/std/format
> >+++ b/libstdc++-v3/include/std/format
> >@@ -4396,12 +4396,12 @@ namespace __format
> >       template<typename _Visitor>
> >       _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> >       visit(this basic_format_arg __arg, _Visitor&& __vis)
> >-      { return __arg._M_visit_user(std::forward<_Visitor>(__vis),
> __arg._M_type); }
> >+      { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
> >
> >       template<typename _Res, typename _Visitor>
> >       _GLIBCXX_CONSTEXPR_FORMAT _Res
> >       visit(this basic_format_arg __arg, _Visitor&& __vis)
> >-      { return __arg._M_visit_user(std::forward<_Visitor>(__vis),
> __arg._M_type); }
> >+      { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
> > #endif
> >
> >     private:
> >@@ -4614,13 +4614,23 @@ namespace __format
> >       friend consteval __format::_Arg_t
> >       __format::__to_arg_t_enum() noexcept;
> >
> >+      [[__gnu__::__noinline__]]
>
> I was wondering what would happen if the compiler inlined this
> function, but the attribute means it won't do that. But as discussed
> with Jakub elsewhere, I'm not 100% sure that the compiler will always
> use the explicit instantiation definition from libstdc++.so -- is it
> possible that it will emit an out-of-line definition in the current
> TU and use that instead of the explicit instantiation?
>
> We could be sure that won't happen by simply not defining the function
> body here, and only defining it in format-inst.cc where we emit the
> explicit instantiation definitions.
>
> >+      handle
> >+      _M_handle_unrecognized() const
> >+      {
> >+      // If new value of _Arg_t is introduced after GCC16, this
> >+      // method should _M_type for it's value and return handle
>
> "method" -> "function"
> "it's" -> "its"
>
> Does this mean to say "should set _M_type"?
>
> I don't understand what the comment is telling me to do. Set _M_type
> to the new _Arg_t value? But isn't it already set to the new value,
> that's why we called _M_handle_unrecognized?
>
> >+      // referencing corresponding alternative in _M_arg_value.
>
Does this helps:
// If _M_type corresponds to the new value of _Arg_t is introduced after
GCC16,
// this method should return handle that reffers to union member of _M_val
// corresponding to it.



>// method should extract value _M_type for it's value and return handle

"method" -> "function"
"it's" -> "its"

Does this mean to say "should set _M_type"?

I don't understand what the comment is telling me to do. Set _M_type
to the new _Arg_t value? But isn't it already set to the new value,
that's why we called _M_handle_unrecognized?



>+      // referencing corresponding alternative in _M_arg_value.




>
> Is _M_arg_value meant to be _M_val?
>
> >+      __throw_format_error("format error: unrecognized argument type");
> >+      }
> >+
> >       template<typename _Visitor>
> >       _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> >-      _M_visit(_Visitor&& __vis, __format::_Arg_t __type)
> >+      _M_visit(_Visitor&& __vis)
> >       {
> >-        using namespace __format;
> >-        switch (__type)
> >+        switch (_M_type)
> >         {
> >+          using enum __format::_Arg_t;
> >           case _Arg_none:
> >             return std::forward<_Visitor>(__vis)(_M_val._M_none);
> >           case _Arg_bool:
> >@@ -4685,13 +4695,17 @@ namespace __format
> >             return std::forward<_Visitor>(__vis)(_M_val._M_u128);
> > #endif
> >           default:
> >-            __builtin_unreachable();
> >+            // 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.
>
> Please wrap this comment to fit in 80 columns.
>
> >+            handle __h = _M_handle_unrecognized();
> >+            return std::forward<_Visitor>(__vis)(__h);
> >         }
> >       }
> >
> >       template<typename _Visitor>
> >       _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> >-      _M_visit_user(_Visitor&& __vis, __format::_Arg_t __type)
> >+      _M_visit_user(_Visitor&& __vis)
> >       {
> >         return _M_visit([&__vis]<typename _Tp>(_Tp& __val) ->
> decltype(auto)
> >           {
> >@@ -4708,7 +4722,7 @@ namespace __format
> >                handle __h(__val);
> >                return std::forward<_Visitor>(__vis)(__h);
> >              }
> >-         }, __type);
> >+        });
> >       }
> >     };
> >
> >@@ -4716,9 +4730,7 @@ namespace __format
> >     _GLIBCXX26_DEPRECATED_SUGGEST("std::basic_format_arg::visit")
> >     inline _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> >     visit_format_arg(_Visitor&& __vis, basic_format_arg<_Context> __arg)
> >-    {
> >-      return __arg._M_visit_user(std::forward<_Visitor>(__vis),
> __arg._M_type);
> >-    }
> >+    { return __arg._M_visit_user(std::forward<_Visitor>(__vis)); }
> >
> > /// @cond undocumented
> > namespace __format
> >@@ -4726,9 +4738,7 @@ namespace __format
> >   template<typename _Visitor, typename _Ctx>
> >     inline _GLIBCXX_CONSTEXPR_FORMAT decltype(auto)
> >     __visit_format_arg(_Visitor&& __vis, basic_format_arg<_Ctx> __arg)
> >-    {
> >-      return __arg._M_visit(std::forward<_Visitor>(__vis),
> __arg._M_type);
> >-    }
> >+    { return __arg._M_visit(std::forward<_Visitor>(__vis)); }
> >
> >   struct _WidthPrecVisitor
> >   {
> >@@ -5061,6 +5071,14 @@ namespace __format
> >       advance_to(iterator __it) { _M_out = std::move(__it); }
> >     };
> >
> >+#if _GLIBCXX_EXTERN_TEMPLATE
> >+   extern template basic_format_arg<format_context>::handle
> >+     basic_format_arg<format_context>::_M_handle_unrecognized() const;
> >+# ifdef _GLIBCXX_USE_WCHAR_T
> >+   extern template basic_format_arg<wformat_context>::handle
> >+     basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
> >+# endif
> >+#endif
> >
> > /// @cond undocumented
> > namespace __format
> >diff --git a/libstdc++-v3/src/c++20/format-inst.cc
> b/libstdc++-v3/src/c++20/format-inst.cc
> >index 65a3f282732..33217ef9c0e 100644
> >--- a/libstdc++-v3/src/c++20/format-inst.cc
> >+++ b/libstdc++-v3/src/c++20/format-inst.cc
> >@@ -27,6 +27,15 @@
> > namespace std
> > {
> > _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >+
> >+ template basic_format_arg<format_context>::handle
> >+   basic_format_arg<format_context>::_M_handle_unrecognized() const;
> >+
> >+# ifdef _GLIBCXX_USE_WCHAR_T
> >+ template basic_format_arg<wformat_context>::handle
> >+   basic_format_arg<wformat_context>::_M_handle_unrecognized() const;
> >+# endif
> >+
> > namespace __format
> > {
> >
> >--
> >2.54.0
> >
> >
>
>

Reply via email to