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