[PATCH] libstdc++: Make dynamic format string checking work with Clang [PR127220]

Tomasz Kaminski tkaminsk@redhat.com
Thu Sep 10 07:43:34 GMT 2026


On Wed, Sep 9, 2026 at 10:28 PM Jonathan Wakely <jwakely@redhat.com> 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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260910/381633f0/attachment-0001.htm>


More information about the Libstdc++ mailing list