This patch exports the __format::__do_vformat_to function in C++23 and
later modes, improving the compile time for the TU using format.

As instantiating __format::__do_vformat_to also triggers instantiation
of formatters for types stored directly in handle (string_view, pointers,
integer, floating points, e.t.c.), we need to handle differences in their
behavior between the standard modes. Currently this encompases handling
of debug format ('?') for string and characters introduces in C++23
(P2510R3 changes for pointers were already treated as DR for C++20).

This patch resolves above by accepting accepting all format specifiers
as extension in the eariel modes, both at runtime and compile time. For
runtime this means that std::format("{:?}", 'a') no longer throw
format_error in C++20. For compile time mode, __builtin_constexpr_diag
is used to emit following warning on such usage:
  warning: constexpr message: escaped output ('?') support in std::format
  is C++23 extension [format_extensions]
This does not apply to new members of formatters, like set_debug_format,
that remain available in only C++23 mode.

In consequence, we can export an single __format::__do_vformat_to
instaintion (build in C++26 mode), in contrast to alternative where,
we have separate C++23 version of (with different ABI tag) for:
 * __format::__do_vformat_to (could use template prameter)
 * formatter specializations for basic_string(_view), char, pointers
 * __formatter_str, __formatter_int.

libstdc++-v3/ChangeLog:

        PR libstc++/126675
        * include/std/format (__format::__warn_extension): Define.
        (__format_str::parse, __formatter_int::_M_do_parse)
        [__glibcxx_format_ranges]: Uncoditionally accept '?' as format
        spec, and call __warn_extension.
        (__formatter_ptr::parse): Remove __glibcxx_format >= 202304L check
        for specifiers accepted as DR.
        (__format::__do_vformat_to): Declare as extern in all standard
        modes.
        * src/c++20/Makefile.am (format-inst.cc): Compile in gnu++26 mode.
        * src/c++20/Makefile.in: Regenerate.
        * testsuite/std/format/debug.cc: Run in C++20, except test for
        set_debug_format.
        * testsuite/std/format/parse_ctx.cc: Modify for '?' being accepted
        in C++20, without warning in runtime format.
        * testsuite/std/format/string.cc: Likewise.
        * testsuite/std/format/string_neg.cc: Check if warning is emitted
        for use of '?' in C++20 mode.
---
>From my testing this gives most of the benefit of PR124410's option
from r16-8719-gfa4754797c that provides out of line instantiations of
std::print, and we may consider removing _GLIBCXX_NO_INLINE_PRINT.

Tested on x86_64 and powerpc64lc. Also tested *format* and *print* in
all standard modes. OK for trunk?

Do we want to remove _GLIBCXX_NO_INLINE_PRINT?

 libstdc++-v3/include/std/format               | 29 ++++++++++++-------
 libstdc++-v3/src/c++20/Makefile.am            |  4 +--
 libstdc++-v3/src/c++20/Makefile.in            |  4 +--
 libstdc++-v3/testsuite/std/format/debug.cc    |  8 ++++-
 .../testsuite/std/format/parse_ctx.cc         | 12 ++------
 libstdc++-v3/testsuite/std/format/string.cc   | 10 ++-----
 .../testsuite/std/format/string_neg.cc        |  4 +++
 7 files changed, 38 insertions(+), 33 deletions(-)

diff --git a/libstdc++-v3/include/std/format b/libstdc++-v3/include/std/format
index f1cd49dd98a..6d1a0828aad 100644
--- a/libstdc++-v3/include/std/format
+++ b/libstdc++-v3/include/std/format
@@ -273,6 +273,15 @@ namespace __format
   __failed_to_parse_format_spec()
   { __throw_format_error("format error: failed to parse format-spec"); }
 
+  [[__gnu__::__always_inline__]]
+  constexpr void
+  __warn_extension(const char* __msg)
+  {
+#if __has_builtin(__builtin_constexpr_diag)
+    __builtin_constexpr_diag (1, "format_extensions", __msg);
+#endif
+  }
+
   template<typename _CharT> class _Scanner;
 
   enum class _Arg_t : unsigned char {
@@ -1456,13 +1465,14 @@ namespace __format
            __spec._M_type = _Pres_s;
            ++__first;
          }
-#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
        else if (*__first == '?')
          {
+#ifndef __glibcxx_format_ranges // C++ >= 23 && HOSTED
+           __warn_extension("escaped output ('?') support in std::format is 
C++23 extension");
+#endif
            __spec._M_debug = true;
            ++__first;
          }
-#endif
 
        if (__finished())
          return __first;
@@ -1694,14 +1704,15 @@ namespace __format
                ++__first;
              }
            break;
-#if __glibcxx_format_ranges // C++ >= 23 && HOSTED
          case '?':
            if (__type == _AsChar)
              {
+#ifndef __glibcxx_format_ranges // C++ >= 23 && HOSTED
+               __warn_extension("escaped output ('?') support in std::format 
is C++23 extension");
+#endif
                __spec._M_debug = true;
                ++__first;
              }
-#endif
            break;
          }
 
@@ -2588,13 +2599,11 @@ namespace __format
        if (__finished())
          return __first;
 
-// _GLIBCXX_RESOLVE_LIB_DEFECTS
-// P2510R3 Formatting pointers
-#if __glibcxx_format >= 202304L
+       // _GLIBCXX_RESOLVE_LIB_DEFECTS
+       // P2510R3 Formatting pointers
        __first = __spec._M_parse_zero_fill(__first, __last);
        if (__finished())
          return __first;
-#endif
 
        __first = __spec._M_parse_width(__first, __last, __pc);
        if (__finished())
@@ -2606,14 +2615,12 @@ namespace __format
            __spec._M_alt = true;
            ++__first;
          }
-#if __glibcxx_format >= 202304L
        else if (*__first == 'P')
          {
            __spec._M_type = _Pres_X;
            __spec._M_alt = true;
            ++__first;
          }
-#endif
 
        if (__finished())
          return __first;
@@ -5609,7 +5616,7 @@ namespace __format
 // instantiates formatters for types stored in basic_format_arg, we can
 // support only single encoding, in this case unicode. This should cover
 // most common use cases.
-#if __cplusplus <= 202002L && _GLIBCXX_EXTERN_TEMPLATE
+#if _GLIBCXX_EXTERN_TEMPLATE
     extern template _Sink_iter<char>
       __do_vformat_to<char, 1>(_Sink_iter<char>, string_view,
                               format_context&);
diff --git a/libstdc++-v3/src/c++20/Makefile.am 
b/libstdc++-v3/src/c++20/Makefile.am
index cfd09bddffc..8ef6e094b5d 100644
--- a/libstdc++-v3/src/c++20/Makefile.am
+++ b/libstdc++-v3/src/c++20/Makefile.am
@@ -63,9 +63,9 @@ format.o: format.cc
 if ENABLE_EXTERN_TEMPLATE
 # The unicode literal version of formatters is exported, override the encoding 
accordingly
 format-inst.lo: format-inst.cc
-       $(LTCXXCOMPILE) -fexec-charset=UTF-8 -c $<
+       $(LTCXXCOMPILE) -std=gnu++26 -fexec-charset=UTF-8 -c $<
 format-inst.o: format-inst.cc
-       $(CXXCOMPILE) -fexec-charset=UTF-8 -c $<
+       $(CXXCOMPILE) -std=gnu++26 -fexec-charset=UTF-8 -c $<
 endif
 
 if GLIBCXX_HOSTED
diff --git a/libstdc++-v3/src/c++20/Makefile.in 
b/libstdc++-v3/src/c++20/Makefile.in
index 067419d2e48..119fb822254 100644
--- a/libstdc++-v3/src/c++20/Makefile.in
+++ b/libstdc++-v3/src/c++20/Makefile.in
@@ -766,9 +766,9 @@ format.o: format.cc
 
 # The unicode literal version of formatters is exported, override the encoding 
accordingly
 @[email protected]: format-inst.cc
-@ENABLE_EXTERN_TEMPLATE_TRUE@  $(LTCXXCOMPILE) -fexec-charset=UTF-8 -c $<
+@ENABLE_EXTERN_TEMPLATE_TRUE@  $(LTCXXCOMPILE) -std=gnu++26 
-fexec-charset=UTF-8 -c $<
 @[email protected]: format-inst.cc
-@ENABLE_EXTERN_TEMPLATE_TRUE@  $(CXXCOMPILE) -fexec-charset=UTF-8 -c $<
+@ENABLE_EXTERN_TEMPLATE_TRUE@  $(CXXCOMPILE) -std=gnu++26 -fexec-charset=UTF-8 
-c $<
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/libstdc++-v3/testsuite/std/format/debug.cc 
b/libstdc++-v3/testsuite/std/format/debug.cc
index 959822b2182..1b7b01393a7 100644
--- a/libstdc++-v3/testsuite/std/format/debug.cc
+++ b/libstdc++-v3/testsuite/std/format/debug.cc
@@ -1,6 +1,6 @@
 // { dg-options "-fexec-charset=UTF-8 -fwide-exec-charset=UTF-32LE 
-DUNICODE_ENC -fconstexpr-ops-limit=500000000" { target le } }
 // { dg-options "-fexec-charset=UTF-8 -fwide-exec-charset=UTF-32BE 
-DUNICODE_ENC -fconstexpr-ops-limit=500000000" { target be } }
-// { dg-do run { target c++23 } }
+// { dg-do run { target c++20 } }
 // { dg-require-effective-target 4byte_wchar_t }
 // { dg-add-options no_pch }
 // { dg-timeout-factor 2 }
@@ -751,6 +751,7 @@ test_char_as_wchar()
   VERIFY( res == LR"('\u{10}'++)" );
 }
 
+#if __cpp_lib_format_ranges
 template<typename T>
 struct DebugWrapper
 {
@@ -832,6 +833,7 @@ test_formatters_c()
   test_formatters<wchar_t>();
   test_formatter_char<wchar_t, char>();
 }
+#endif
 
 CONSTEXPR bool
 test_all()
@@ -867,7 +869,9 @@ test_all()
 
   test_padding();
 
+#if __cpp_lib_format_ranges
   test_formatters_c();
+#endif
 
   return true;
 }
@@ -880,3 +884,5 @@ int main()
 {
   test_all();
 }
+
+// { dg-warning "escaped output.*extension" "" { target c++20_down } 0 } 
diff --git a/libstdc++-v3/testsuite/std/format/parse_ctx.cc 
b/libstdc++-v3/testsuite/std/format/parse_ctx.cc
index 6294dcf43f5..092f83ff328 100644
--- a/libstdc++-v3/testsuite/std/format/parse_ctx.cc
+++ b/libstdc++-v3/testsuite/std/format/parse_ctx.cc
@@ -108,12 +108,6 @@ is_std_format_spec_for(std::string_view spec)
   }
 }
 
-#if __cpp_lib_format_ranges
-constexpr bool escaped_strings_supported = true;
-#else
-constexpr bool escaped_strings_supported = false;
-#endif
-
 void
 test_char()
 {
@@ -149,7 +143,7 @@ test_char()
   VERIFY( is_std_format_spec_for<char>("x") );
   VERIFY( is_std_format_spec_for<char>("X") );
   VERIFY( ! is_std_format_spec_for<char>("s") );
-  VERIFY( is_std_format_spec_for<char>("?") == escaped_strings_supported );
+  VERIFY( is_std_format_spec_for<char>("?") );
   VERIFY( ! is_std_format_spec_for<char>("a") );
   VERIFY( ! is_std_format_spec_for<char>("A") );
   VERIFY( ! is_std_format_spec_for<char>("f") );
@@ -395,7 +389,7 @@ test_string()
   VERIFY( ! is_std_format_spec_for<const char*>("x") );
   VERIFY( ! is_std_format_spec_for<const char*>("X") );
   VERIFY( is_std_format_spec_for<const char*>("s") );
-  VERIFY( is_std_format_spec_for<const char*>("?") == 
escaped_strings_supported );
+  VERIFY( is_std_format_spec_for<const char*>("?") );
   VERIFY( ! is_std_format_spec_for<const char*>("p") );
   VERIFY( ! is_std_format_spec_for<const char*>("P") );
   VERIFY( ! is_std_format_spec_for<const char*>("a") );
@@ -407,7 +401,7 @@ test_string()
 
   VERIFY( is_std_format_spec_for<const char*>("*^6s") );
   VERIFY( is_std_format_spec_for<const char*>(">6s") );
-  VERIFY( is_std_format_spec_for<const char*>("_<6.4?") == 
escaped_strings_supported );
+  VERIFY( is_std_format_spec_for<const char*>("_<6.4?") );
 }
 
 struct S { };
diff --git a/libstdc++-v3/testsuite/std/format/string.cc 
b/libstdc++-v3/testsuite/std/format/string.cc
index 004690ad8da..4093b8ac571 100644
--- a/libstdc++-v3/testsuite/std/format/string.cc
+++ b/libstdc++-v3/testsuite/std/format/string.cc
@@ -69,12 +69,6 @@ test_indexing()
   VERIFY( ! is_format_string_for("{} {0}", 1) );
 }
 
-#if __cpp_lib_format_ranges
-constexpr bool escaped_strings_supported = true;
-#else
-constexpr bool escaped_strings_supported = false;
-#endif
-
 CONSTEXPR void
 test_format_spec()
 {
@@ -95,8 +89,8 @@ test_format_spec()
   
   VERIFY( is_format_string_for("{:#d}", 'c') );
   VERIFY( is_format_string_for("{:#d}", true) );
-  VERIFY( is_format_string_for("{0:s} {0:?}", "str") == 
escaped_strings_supported );
-  VERIFY( is_format_string_for("{0:} {0:?}", 'c') == escaped_strings_supported 
);
+  VERIFY( is_format_string_for("{0:s} {0:?}", "str") );
+  VERIFY( is_format_string_for("{0:} {0:?}", 'c') );
 
   // Invalid sign options.
   VERIFY( ! is_format_string_for("{:+}", "str") );
diff --git a/libstdc++-v3/testsuite/std/format/string_neg.cc 
b/libstdc++-v3/testsuite/std/format/string_neg.cc
index acae88eaf71..3a3e7a6166e 100644
--- a/libstdc++-v3/testsuite/std/format/string_neg.cc
+++ b/libstdc++-v3/testsuite/std/format/string_neg.cc
@@ -9,4 +9,8 @@ struct X { };
 std::format_string<X> str(""); // { dg-error "here" }
 // { dg-error "std::formatter must be specialized" "" { target *-*-* } 0 }
 
+auto s2 = std::format("{:?}", "abcd"); // { dg-warning "expansion of" "" { 
target c++20_down } 0 }
+// { dg-warning "warning.*escaped output.*extension" "" { target c++20_down } 
0 }
+// { dg-bogus "warning.*escaped output.*extension" "" { target c++23 } 0 }
+
 // { dg-prune-output "use of deleted function" }
-- 
2.55.0

Reply via email to