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