]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Formatting std::thread::id and std::stacktrace (P2693R1)
authorJonathan Wakely <jwakely@redhat.com>
Wed, 16 Aug 2023 16:10:51 +0000 (17:10 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Mon, 11 Sep 2023 13:56:58 +0000 (14:56 +0100)
New std::formatter specializations for C++23.

libstdc++-v3/ChangeLog:

* include/bits/version.def (__cpp_lib_formatters): Define.
* include/bits/version.h: Regenerate.
* include/std/stacktrace (formatter<stacktrace_entry>)
(formatter<basic_stacktrace<Alloc>>): Define.
* include/std/thread (formatter<thread::id, charT>): Define.
* testsuite/19_diagnostics/stacktrace/output.cc: New test.
* testsuite/19_diagnostics/stacktrace/synopsis.cc: Add
std::formatter specializations.
* testsuite/19_diagnostics/stacktrace/version.cc: Check
__cpp_lib_formatters macro.
* testsuite/30_threads/thread/id/hash.cc: Remove gthreads
dependency.
* testsuite/30_threads/thread/id/operators.cc: Likewise.
* testsuite/30_threads/thread/id/operators_c++20.cc: Likewise.
* testsuite/30_threads/thread/id/output.cc: New test.

libstdc++-v3/include/bits/version.def
libstdc++-v3/include/bits/version.h
libstdc++-v3/include/std/stacktrace
libstdc++-v3/include/std/thread
libstdc++-v3/testsuite/19_diagnostics/stacktrace/output.cc [new file with mode: 0644]
libstdc++-v3/testsuite/19_diagnostics/stacktrace/synopsis.cc
libstdc++-v3/testsuite/19_diagnostics/stacktrace/version.cc
libstdc++-v3/testsuite/30_threads/thread/id/hash.cc
libstdc++-v3/testsuite/30_threads/thread/id/operators.cc
libstdc++-v3/testsuite/30_threads/thread/id/operators_c++20.cc
libstdc++-v3/testsuite/30_threads/thread/id/output.cc [new file with mode: 0644]

index da8d067dd1abe0bd9140bd3ad3f366f305c1d8fe..8d9b2f71a2e387d2e72c6cf0ec400b7bffc20826 100644 (file)
@@ -1526,6 +1526,15 @@ ftms = {
   };
 };
 
+ftms = {
+  name = formatters;
+  values = {
+    v = 202302;
+    cxxmin = 23;
+    hosted = yes;
+  };
+};
+
 ftms = {
   name = ios_noreplace;
   values = {
index 22cc21119c565feb4768b5750fdd739cc11237e5..3c20a8a51fb83826330216603e7c756f2efa9af1 100644 (file)
 #undef __glibcxx_want_adaptor_iterator_pair_constructor
 
 // from version.def line 1530
+#if !defined(__cpp_lib_formatters)
+# if (__cplusplus >= 202302L) && _GLIBCXX_HOSTED
+#  define __glibcxx_formatters 202302L
+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_formatters)
+#   define __cpp_lib_formatters 202302L
+#  endif
+# endif
+#endif /* !defined(__cpp_lib_formatters) && defined(__glibcxx_want_formatters) */
+#undef __glibcxx_want_formatters
+
+// from version.def line 1539
 #if !defined(__cpp_lib_ios_noreplace)
 # if (__cplusplus >= 202302L) && _GLIBCXX_HOSTED
 #  define __glibcxx_ios_noreplace 202207L
 #endif /* !defined(__cpp_lib_ios_noreplace) && defined(__glibcxx_want_ios_noreplace) */
 #undef __glibcxx_want_ios_noreplace
 
-// from version.def line 1539
+// from version.def line 1548
 #if !defined(__cpp_lib_move_only_function)
 # if (__cplusplus >= 202302L) && _GLIBCXX_HOSTED
 #  define __glibcxx_move_only_function 202110L
 #endif /* !defined(__cpp_lib_move_only_function) && defined(__glibcxx_want_move_only_function) */
 #undef __glibcxx_want_move_only_function
 
-// from version.def line 1548
+// from version.def line 1557
 #if !defined(__cpp_lib_spanstream)
 # if (__cplusplus >= 202302L) && _GLIBCXX_HOSTED && (__glibcxx_span)
 #  define __glibcxx_spanstream 202106L
 #endif /* !defined(__cpp_lib_spanstream) && defined(__glibcxx_want_spanstream) */
 #undef __glibcxx_want_spanstream
 
-// from version.def line 1558
+// from version.def line 1567
 #if !defined(__cpp_lib_stacktrace)
 # if (__cplusplus >= 202302L) && _GLIBCXX_HOSTED && (_GLIBCXX_HAVE_STACKTRACE)
 #  define __glibcxx_stacktrace 202011L
 #endif /* !defined(__cpp_lib_stacktrace) && defined(__glibcxx_want_stacktrace) */
 #undef __glibcxx_want_stacktrace
 
-// from version.def line 1568
+// from version.def line 1577
 #if !defined(__cpp_lib_string_contains)
 # if (__cplusplus >= 202302L) && _GLIBCXX_HOSTED
 #  define __glibcxx_string_contains 202011L
 #endif /* !defined(__cpp_lib_string_contains) && defined(__glibcxx_want_string_contains) */
 #undef __glibcxx_want_string_contains
 
-// from version.def line 1577
+// from version.def line 1586
 #if !defined(__cpp_lib_string_resize_and_overwrite)
 # if (__cplusplus >= 202302L) && _GLIBCXX_HOSTED
 #  define __glibcxx_string_resize_and_overwrite 202110L
 #endif /* !defined(__cpp_lib_string_resize_and_overwrite) && defined(__glibcxx_want_string_resize_and_overwrite) */
 #undef __glibcxx_want_string_resize_and_overwrite
 
-// from version.def line 1586
+// from version.def line 1595
 #if !defined(__cpp_lib_ratio)
 # if (__cplusplus >  202302L)
 #  define __glibcxx_ratio 202306L
 #endif /* !defined(__cpp_lib_ratio) && defined(__glibcxx_want_ratio) */
 #undef __glibcxx_want_ratio
 
-// from version.def line 1594
+// from version.def line 1603
 #if !defined(__cpp_lib_to_string)
 # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (__glibcxx_to_chars)
 #  define __glibcxx_to_string 202306L
index 358a81b82e5da4c7ba83ac7b4cdad8ed346187bc..da0e48d35329e61d35213fdf8a46f722d2e816c3 100644 (file)
 #include <bits/c++config.h>
 
 #define __glibcxx_want_stacktrace
+#define __glibcxx_want_formatters
 #include <bits/version.h>
 
 #ifdef __cpp_lib_stacktrace // C++ >= 23 && hosted && HAVE_STACKTRACE
 #include <compare>
+#include <format>
 #include <new>
 #include <string>
 #include <sstream>
@@ -692,6 +694,84 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       return std::move(__os).str();
     }
 
+  template<>
+    class formatter<stacktrace_entry>
+    {
+    public:
+      constexpr typename basic_format_parse_context<char>::iterator
+      parse(basic_format_parse_context<char>& __pc)
+      {
+       __format::_Spec<char> __spec{};
+       const auto __last = __pc.end();
+       auto __first = __pc.begin();
+
+       auto __finalize = [this, &__spec] {
+         _M_spec = __spec;
+       };
+
+       auto __finished = [&] {
+         if (__first == __last || *__first == '}')
+           {
+             __finalize();
+             return true;
+           }
+         return false;
+       };
+
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_fill_and_align(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_width(__first, __last, __pc);
+       if (__finished())
+         return __first;
+
+       __throw_format_error("format error: invalid format-spec for "
+                            "std::stacktrace_entry");
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, char>::iterator
+       format(const stacktrace_entry& __x,
+              basic_format_context<_Out, char>& __fc) const
+       {
+         std::ostringstream __os;
+         __os << __x;
+         return __format::__write(__fc.out(), __os.view());
+       }
+
+    private:
+      __format::_Spec<char> _M_spec;
+    };
+
+  template<typename _Allocator>
+    class formatter<basic_stacktrace<_Allocator>>
+    {
+    public:
+      constexpr typename basic_format_parse_context<char>::iterator
+      parse(basic_format_parse_context<char>& __pc)
+      {
+       const auto __first = __pc.begin();
+       if (__first == __pc.end() || *__first == '}')
+         return __first;
+       __throw_format_error("format error: invalid format-spec for "
+                            "std::basic_stacktrace");
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, char>::iterator
+       format(const basic_stacktrace<_Allocator>& __x,
+              basic_format_context<_Out, char>& __fc) const
+       {
+         std::ostringstream __os;
+         __os << __x;
+         return __format::__write(__fc.out(), __os.view());
+       }
+    };
+
   namespace pmr
   {
     using stacktrace
index 28582c9df5ce91dfcaefac97698db75fa375d0d1..c182a4d56c1de9782fdca0a640447757a55bcad0 100644 (file)
 # include <stop_token> // std::stop_source, std::stop_token, std::nostopstate
 #endif
 
+#if __cplusplus >= 202302L
+# include <format>
+#endif
+
 #include <bits/std_thread.h> // std::thread, get_id, yield
 #include <bits/this_thread_sleep.h> // std::this_thread::sleep_for, sleep_until
 
 #define __glibcxx_want_jthread
+#define __glibcxx_want_formatters
 #include <bits/version.h>
 
 namespace std _GLIBCXX_VISIBILITY(default)
@@ -281,6 +286,63 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   };
 #endif // __cpp_lib_jthread
 
+#ifdef __cpp_lib_formatters // C++ >= 23
+
+  template<typename _CharT>
+    class formatter<thread::id, _CharT>
+    {
+    public:
+      constexpr typename basic_format_parse_context<_CharT>::iterator
+      parse(basic_format_parse_context<_CharT>& __pc)
+      {
+       __format::_Spec<_CharT> __spec{};
+       const auto __last = __pc.end();
+       auto __first = __pc.begin();
+
+       auto __finalize = [this, &__spec] {
+         _M_spec = __spec;
+       };
+
+       auto __finished = [&] {
+         if (__first == __last || *__first == '}')
+           {
+             __finalize();
+             return true;
+           }
+         return false;
+       };
+
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_fill_and_align(__first, __last);
+       if (__finished())
+         return __first;
+
+       __first = __spec._M_parse_width(__first, __last, __pc);
+       if (__finished())
+         return __first;
+
+       __throw_format_error("format error: invalid format-spec for "
+                            "std::thread::id");
+      }
+
+      template<typename _Out>
+       typename basic_format_context<_Out, _CharT>::iterator
+       format(thread::id __id, basic_format_context<_Out, _CharT>& __fc) const
+       {
+         std::basic_ostringstream<_CharT> __os;
+         __os << __id;
+         auto __str = __os.view();
+         return __format::__write_padded_as_spec(__str, __str.size(),
+                                                 __fc, _M_spec);
+       }
+
+    private:
+      __format::_Spec<_CharT> _M_spec;
+    };
+#endif // __cpp_lib_formatters
+
   /// @} group threads
 
 _GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/testsuite/19_diagnostics/stacktrace/output.cc b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/output.cc
new file mode 100644 (file)
index 0000000..5116413
--- /dev/null
@@ -0,0 +1,58 @@
+// { dg-options "-std=gnu++23" }
+// { dg-do compile { target c++23 } }
+// { dg-require-effective-target stacktrace }
+
+#include <stacktrace>
+#include <sstream>
+#include <testsuite_hooks.h>
+
+#ifndef __cpp_lib_formatters
+# error "Feature-test macro for formatters missing in <stacktrace>"
+#elif __cpp_lib_formatters < 202302L
+# error "Feature-test macro for formatters has wrong value in <stacktrace>"
+#endif
+
+void
+test_to_string()
+{
+  auto trace = std::stacktrace::current();
+  std::string s1 = std::to_string(trace.at(0));
+  VERIFY( s1.contains("test_to_string():15") );
+  std::string s2 = std::to_string(trace);
+  VERIFY( s2.contains(s1) );
+}
+
+void
+test_ostream()
+{
+  std::ostringstream out;
+  auto trace = std::stacktrace::current();
+  out << trace.at(0);
+  VERIFY( out.str() == std::to_string(trace.at(0)) );
+  out.str("");
+  out << trace;
+  VERIFY( out.str() == std::to_string(trace) );
+}
+
+void
+test_format()
+{
+  static_assert( std::is_default_constructible_v<std::formatter<std::stacktrace_entry, char>> );
+  static_assert( std::is_default_constructible_v<std::formatter<std::stacktrace, char>> );
+  static_assert( std::is_default_constructible_v<std::formatter<std::pmr::stacktrace, char>> );
+
+  auto trace = std::pmr::stacktrace::current();
+  VERIFY( std::format("{}", trace) == std::to_string(trace) );
+
+  std::stacktrace_entry entry = trace.at(0);
+  std::string str = std::to_string(entry);
+  VERIFY( std::format("{}", entry) == str );
+  VERIFY( std::format("{0:!<{1}}", entry, str.size() + 3) == (str + "!!!") );
+}
+
+int main()
+{
+  test_to_string();
+  test_ostream();
+  test_format();
+}
index 262abea21ecfa23a9e3e845e134ebeec452b8f20..ece5d526fb9177cebaf415ec4a58115b5c92602a 100644 (file)
@@ -35,6 +35,9 @@ namespace std
     ostream&
     operator<<(ostream& os, const basic_stacktrace<Allocator>& st);
 
+  template<> struct formatter<stacktrace_entry>;
+  template<class Allocator> struct formatter<basic_stacktrace<Allocator>>;
+
   namespace pmr {
     using stacktrace = basic_stacktrace<polymorphic_allocator<stacktrace_entry>>;
   }
index ed466be5a36264fbeef55b65cd4614880b49d210..d59a0696c256424165bdfa02009c1ca2a0ea9e61 100644 (file)
@@ -9,3 +9,9 @@
 #elif __cpp_lib_stacktrace < 202011L
 # error "Feature-test macro for stacktrace has wrong value in <version>"
 #endif
+
+#ifndef __cpp_lib_formatters
+# error "Feature-test macro for formatters missing in <version>"
+#elif __cpp_lib_formatters < 202302L
+# error "Feature-test macro for formatters has wrong value in <version>"
+#endif
index 6af80e5c730f0a8ba60d1457ee30963eabe255dc..afbae8f2b908cdc4ddab464a4254f2073dcc1bd2 100644 (file)
@@ -1,6 +1,4 @@
 // { dg-do compile { target c++11 } }
-// { dg-require-cstdint "" }
-// { dg-require-gthreads "" }
 
 // Copyright (C) 2010-2023 Free Software Foundation, Inc.
 //
index 88ec17c33bebf0166691c93ed34522a5e5ec0d84..c47a9e262b476b871b1d7c80b5b3fa0c1a0dd7eb 100644 (file)
@@ -1,5 +1,4 @@
 // { dg-do compile { target c++11 } }
-// { dg-require-gthreads "" }
 
 // Copyright (C) 2009-2023 Free Software Foundation, Inc.
 //
index 9432ec3468fbad91587d74944433c5352510f6c0..667b6a3dcf5ae7c909025e6263cb6c442134ac01 100644 (file)
@@ -1,6 +1,5 @@
 // { dg-options "-std=gnu++2a" }
 // { dg-do compile { target c++2a } }
-// { dg-require-gthreads "" }
 
 // Copyright (C) 2020-2023 Free Software Foundation, Inc.
 //
diff --git a/libstdc++-v3/testsuite/30_threads/thread/id/output.cc b/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
new file mode 100644 (file)
index 0000000..08d8c89
--- /dev/null
@@ -0,0 +1,103 @@
+// { dg-do run { target c++11 } }
+// { dg-additional-options "-pthread" { target pthread } }
+// { dg-require-gthreads "" }
+
+#include <thread>
+#include <sstream>
+#include <format>
+#include <testsuite_hooks.h>
+
+void
+test01()
+{
+  std::ostringstream out;
+  std::thread::id i{}, j{};
+  out << i;
+  auto s0 = out.str();
+  VERIFY( s0 == "thread::id of a non-executing thread" );
+  out.str("");
+  out << j;
+  VERIFY( out.str() == s0 );
+
+  std::thread t1([]{});
+  j = t1.get_id();
+  out.str("");
+  out << j;
+  auto s1 = out.str();
+  VERIFY( s1 != s0 );
+  auto j2 = j;
+  out.str("");
+  out << j2;
+  VERIFY( out.str() == s1 );
+
+  std::thread t2([]{});
+  j2 = t2.get_id();
+  out.str("");
+  out << j2;
+  auto s2 = out.str();
+  VERIFY( s2 != s0 );
+  VERIFY( s2 != s1 );
+
+#ifdef _GLIBCXX_USE_WCHAR_T
+  std::wostringstream wout;
+  wout << i;
+  auto ws0 = wout.str();
+  wout.str(L"");
+  wout << j;
+  auto ws1 = wout.str();
+  wout.str(L"");
+  wout << j2;
+  auto ws2 = wout.str();
+  wout.str(L"");
+
+  wout << s0.c_str() << ' ' << s1.c_str() << ' ' << s2.c_str();
+  VERIFY( wout.str() == (ws0 + L' ' + ws1 + L' ' + ws2) );
+#endif
+
+  t1.join();
+  t2.join();
+}
+
+void
+test02()
+{
+#if __cpp_lib_formatters >= 202302
+
+  static_assert( std::is_default_constructible_v<std::formatter<std::thread::id, char>> );
+
+  std::thread t1([]{});
+  std::thread t2([]{});
+  std::ostringstream out;
+  std::thread::id i{};
+  std::thread::id j = t1.get_id();
+  std::thread::id k = t2.get_id();
+  out << i << ' ' << j << ' ' << k;
+  VERIFY( std::format("{} {} {}", i, j, k) == out.str() );
+
+  out.str("");
+  out << j;
+  auto s1 = out.str();
+  auto len = s1.size();
+  out.str("");
+
+  auto s2 = std::format("{0:x^{1}}", j, len + 4);
+  VERIFY( s2 == ("xx" + s1 + "xx") );
+
+#ifdef _GLIBCXX_USE_WCHAR_T
+  static_assert( std::is_default_constructible_v<std::formatter<std::thread::id, wchar_t>> );
+  auto ws1 = std::format(L"{}", j);
+  VERIFY( ws1.length() == len );
+#endif
+
+  t1.join();
+  t2.join();
+#elif __cplusplus >= 202302L
+# error "Feature-test macro for formatters has wrong value in <thread>"
+#endif
+}
+
+int main()
+{
+  test01();
+  test02();
+}