On Wed, Sep 9, 2026 at 10:28 PM Jonathan Wakely <[email protected]> wrote:
> Clang cannot compile format_parse_context::check_dynamic_spec due to a > longstanding bug[1] with out-of-line function templates called during > constant evaluation. This patch refactors check_dynamic_spec and related > functions, so that the definitions are inline in the class body. > > To make this work we move the definition of __format::_Arg_t earlier, > and redefine _Scanner<C>::_Parse_context as a nested class within > basic_format_parse_context<C>, called _Scan_parse_context. The > __check_dynamic_spec function is changed to take a span<_Arg_t> instead > of being instantiated with a template parameter pack of arg types. The > callers that have fixed arg types can pass in an array of the fixed > integer or string types, and the generic form (which is rarely needed in > user code) does the conversion using the __to_arg_t_enum function. > > This allows the integer and string forms to work with Clang, as they no > longer need to instantiate an out-of-line function template. The generic > check_dynamic_spec<T...>() form still won't work with Clang until they > fix their bug, but most users will never notice that. > > [1]: https://github.com/llvm/llvm-project/issues/73232 > > libstdc++-v3/ChangeLog: > > PR libstdc++/127220 > * include/std/format (__format::_Arg_t): Move definition > earlier. > (basic_format_parse_context::check_dynamic_spec): Construct > array of _Arg_t from template parameter list. > (basic_format_parse_context::check_dynamic_spec_integral): > Construct array of _Arg_t based on supported types. > (basic_format_parse_context::check_dynamic_spec_string): > Likewise. > (basic_format_parse_context::__check_dynamic_spec): Replace > template parameter list with span<const _Arg_t> function > parameter and simplify accordingly. Define in class body. > (basic_format_parse_context::_Scan_parse_context): New struct > the replaces _Scanner::_Parse_context. > (__format::_Scanner::_M_pc): Change type to _Scan_parse_context. > --- > > Tested x86_64-linux, and manually confirmed that the testcase in the PR > works with Clang 21.1.8 > LGTM, which is not unexepcted, as I have already seen most of this patch. > > libstdc++-v3/include/std/format | 126 ++++++++++++++++---------------- > 1 file changed, 63 insertions(+), 63 deletions(-) > > diff --git a/libstdc++-v3/include/std/format > b/libstdc++-v3/include/std/format > index 730c309a97d3..90576f022705 100644 > --- a/libstdc++-v3/include/std/format > +++ b/libstdc++-v3/include/std/format > @@ -275,6 +275,24 @@ namespace __format > > template<typename _CharT> class _Scanner; > > + 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, > + _Arg_max_, > + > +#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > + _Arg_ibm128 = _Arg_ldbl, > + _Arg_ieee128 = _Arg_float128, > +#endif > + }; > + using enum _Arg_t; > + > + template<typename _CharT, typename _Tp> > + consteval _Arg_t > + __to_arg_t_enum() noexcept; > + > } // namespace __format > /// @endcond > > @@ -337,7 +355,7 @@ namespace __format > __format::__invalid_arg_id_in_format_string(); > } > > -#if __cpp_lib_format >= 202305L > +#if __cpp_lib_format >= 202305L // >= C++26 > template<typename... _Ts> > constexpr void > check_dynamic_spec(size_t __id) noexcept > @@ -345,17 +363,22 @@ namespace __format > static_assert(__valid_types_for_check_dynamic_spec<_Ts...>(), > "template arguments for > check_dynamic_spec<Ts...>(id) " > "must be unique and must be one of the allowed > types"); > - if consteval { > - __check_dynamic_spec<_Ts...>(__id); > - } > + if constexpr (sizeof...(_Ts)) > + if consteval { > + const __format::_Arg_t __t[] = { > + __format::__to_arg_t_enum<_CharT, _Ts>()... > + }; > + __check_dynamic_spec(__id, __t); > + } > } > > constexpr void > check_dynamic_spec_integral(size_t __id) noexcept > { > if consteval { > - __check_dynamic_spec<int, unsigned, long long, > - unsigned long long>(__id); > + using enum __format::_Arg_t; > + const __format::_Arg_t __t[] = { _Arg_i, _Arg_u, _Arg_ll, > _Arg_ull }; > + __check_dynamic_spec(__id, __t); > } > } > > @@ -363,7 +386,9 @@ namespace __format > check_dynamic_spec_string(size_t __id) noexcept > { > if consteval { > - __check_dynamic_spec<const _CharT*, > basic_string_view<_CharT>>(__id); > + using enum __format::_Arg_t; > + const __format::_Arg_t __t[] = { _Arg_str, _Arg_sv }; > + __check_dynamic_spec(__id, __t); > } > } > > @@ -403,14 +428,27 @@ namespace __format > } > } > > - template<typename... _Ts> > - consteval void > - __check_dynamic_spec(size_t __id) noexcept; > + // Common implementation of check_dynamic_spec{,_string,_integral} > + consteval void > + __check_dynamic_spec(size_t __id, > + span<const __format::_Arg_t> __types) noexcept > + { > + if (__id >= _M_num_args) > + __format::__invalid_arg_id_in_format_string(); > + > + if (auto* __args = > static_cast<_Scan_parse_context*>(this)->_M_types) > + { > + for (auto __t : __types) > + if (__args[__id] == __t) > + return; > + > + __invalid_dynamic_spec("arg(id) type does not match"); > + } > + // else this is a formatting scanner, do not do any type checks. > + } > > // This must not be constexpr. > static void __invalid_dynamic_spec(const char*); > - > - friend __format::_Scanner<_CharT>; > #endif > > // This constructor should only be used by the implementation. > @@ -427,6 +465,18 @@ namespace __format > _Indexing _M_indexing = _Unknown; > size_t _M_next_arg_id = 0; > size_t _M_num_args = 0; > + > + // Derived parse context used by Scanner when checking format > strings. > + struct _Scan_parse_context; > + friend __format::_Scanner<_CharT>; > + }; > + > + template<typename _CharT> > + struct basic_format_parse_context<_CharT>::_Scan_parse_context > + : basic_format_parse_context<_CharT> > + { > + using > basic_format_parse_context<_CharT>::basic_format_parse_context; > + const __format::_Arg_t* _M_types = nullptr; > }; > > /// @cond undocumented > @@ -4107,20 +4157,6 @@ namespace __format > } > }; > > - 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, > - _Arg_max_, > - > -#ifdef _GLIBCXX_LONG_DOUBLE_ALT128_COMPAT > - _Arg_ibm128 = _Arg_ldbl, > - _Arg_ieee128 = _Arg_float128, > -#endif > - }; > - using enum _Arg_t; > - > template<typename _Context> > struct _Arg_value > { > @@ -5066,11 +5102,7 @@ namespace __format > { > using iterator = typename > basic_format_parse_context<_CharT>::iterator; > > - struct _Parse_context : basic_format_parse_context<_CharT> > - { > - using > basic_format_parse_context<_CharT>::basic_format_parse_context; > - const _Arg_t* _M_types = nullptr; > - } _M_pc; > + typename basic_format_parse_context<_CharT>::_Scan_parse_context > _M_pc; > > constexpr explicit > _Scanner(basic_string_view<_CharT> __str, size_t __nargs = > (size_t)-1) > @@ -5437,38 +5469,6 @@ namespace __format > } // namespace __format > /// @endcond > > -#if __cpp_lib_format >= 202305L // >= C++26 > - /// @cond undocumented > - // Common implementation of check_dynamic_spec{,_string,_integral} > - template<typename _CharT> > - template<typename... _Ts> > - consteval void > - basic_format_parse_context<_CharT>:: > - __check_dynamic_spec(size_t __id) noexcept > - { > - if (__id >= _M_num_args) > - __format::__invalid_arg_id_in_format_string(); > - if constexpr (sizeof...(_Ts) != 0) > - { > - using _Parse_ctx = __format::_Scanner<_CharT>::_Parse_context; > - auto* __args = static_cast<_Parse_ctx*>(this)->_M_types; > - // Formatting scanner, no type check. > - if (!__args) > - return; > - > - auto __arg = __args[__id]; > - __format::_Arg_t __types[] = { > - __format::__to_arg_t_enum<_CharT, _Ts>()... > - }; > - for (auto __t : __types) > - if (__arg == __t) > - return; > - } > - __invalid_dynamic_spec("arg(id) type does not match"); > - } > - /// @endcond > -#endif > - > template<typename _CharT, typename... _Args> > template<typename _Tp> > requires convertible_to<const _Tp&, basic_string_view<_CharT>> > -- > 2.55.0 > >
