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

 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

Reply via email to