On Tue, 7 Jul 2026 at 12:43, Tomasz Kamiński <[email protected]> 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 aliment), and thus expanding the values in _Arg_t.
> However, any such value would be unrecognized by the TUs compiled with
> 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 (this is ensured by not providing
> template definition in header in such case).
> In consequence, the implementation may return handle object referencing
> new alternatives (such wrapping is already required to provide standard
> compliant behavior of visit_format_arg).
> As no new _Arg_t were introduced since GCC16, this method simply throws
> format_error now, and contains appropriate 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.
> 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 misuse,
> 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):
> Declare with explicit specializations for (w)format_context
> if _GLIBCXX_EXTERN_TEMPLATE, and define otherwise.
> (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): Define and export
> explicit specializations for (w)format_context.
>
> Reviewed-by: Jonathan Wakely <[email protected]>
> Signed-off-by: Tomasz Kamiński <[email protected]>
> ---
> v2 removes the defintion of _M_handle_unrecognized template
> from the header, and moves it to source file if _GLIBCXX_EXTERN_TEMPLATE,
> is defined. Add revelant comment to the source file and commit description.
> Also addresses the other review comments.
>
> Tested on x86_64-linux locally. OK for trunk?
OK thanks
>
> libstdc++-v3/config/abi/pre/gnu.ver | 4 +++
> libstdc++-v3/include/std/format | 52 +++++++++++++++++++--------
> libstdc++-v3/src/c++20/format-inst.cc | 19 ++++++++++
> 3 files changed, 61 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 dd275b6b817..516af6f14c8 100644
> --- a/libstdc++-v3/include/std/format
> +++ b/libstdc++-v3/include/std/format
> @@ -4404,12 +4404,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:
> @@ -4622,13 +4622,17 @@ namespace __format
> friend consteval __format::_Arg_t
> __format::__to_arg_t_enum() noexcept;
>
> + [[__gnu__::__noinline__]]
> + handle
> + _M_handle_unrecognized() const;
> +
> 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:
> @@ -4693,13 +4697,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.
> + 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)
> {
> @@ -4716,7 +4724,7 @@ namespace __format
> handle __h(__val);
> return std::forward<_Visitor>(__vis)(__h);
> }
> - }, __type);
> + });
> }
> };
>
> @@ -4724,9 +4732,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
> @@ -4734,9 +4740,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
> {
> @@ -5069,6 +5073,26 @@ namespace __format
> advance_to(iterator __it) { _M_out = std::move(__it); }
> };
>
> +#if _GLIBCXX_EXTERN_TEMPLATE
> + // The defintion _M_handle_unrecognized is placed in format-inst.cc
> + // source file, to ensure that it will not be inlined by compiler.
> + 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
> +#else
> + template<typename _Context>
> + typename basic_format_arg<_Context>::handle
> + basic_format_arg<_Context>::_M_handle_unrecognized() const
> + {
> + // If _M_type corresponds to a new value of _Arg_t introduced after
> + // GCC 16, this function should return a handle that refers to the
> + // union member of _M_val corresponding to that _Arg_t value.
> + __throw_format_error("format error: unrecognized argument type");
> + }
> +#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..fe15992485e 100644
> --- a/libstdc++-v3/src/c++20/format-inst.cc
> +++ b/libstdc++-v3/src/c++20/format-inst.cc
> @@ -27,6 +27,25 @@
> namespace std
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
> +
> + template<typename _Context>
> + typename basic_format_arg<_Context>::handle
> + basic_format_arg<_Context>::_M_handle_unrecognized() const
> + {
> + // If _M_type corresponds to a new value of _Arg_t introduced after
> GCC 16,
> + // this function should return a handle that refers to the union
> member of
> + // _M_val corresponding to that _Arg_t value.
> + __throw_format_error("format error: unrecognized argument type");
> + }
> +
> + 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
>