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.

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