[committed] libstdc++: Formatting std::thread::id and std::stacktrace (P2693R1)

Jonathan Wakely jwakely@redhat.com
Mon Sep 11 13:57:08 GMT 2023


Tested aarch64-linux. Pushed to trunk.

-- >8 --

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         |   9 ++
 libstdc++-v3/include/bits/version.h           |  25 +++--
 libstdc++-v3/include/std/stacktrace           |  80 ++++++++++++++
 libstdc++-v3/include/std/thread               |  62 +++++++++++
 .../19_diagnostics/stacktrace/output.cc       |  58 ++++++++++
 .../19_diagnostics/stacktrace/synopsis.cc     |   3 +
 .../19_diagnostics/stacktrace/version.cc      |   6 +
 .../testsuite/30_threads/thread/id/hash.cc    |   2 -
 .../30_threads/thread/id/operators.cc         |   1 -
 .../30_threads/thread/id/operators_c++20.cc   |   1 -
 .../testsuite/30_threads/thread/id/output.cc  | 103 ++++++++++++++++++
 11 files changed, 339 insertions(+), 11 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/19_diagnostics/stacktrace/output.cc
 create mode 100644 libstdc++-v3/testsuite/30_threads/thread/id/output.cc

diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
index da8d067dd1a..8d9b2f71a2e 100644
--- a/libstdc++-v3/include/bits/version.def
+++ b/libstdc++-v3/include/bits/version.def
@@ -1526,6 +1526,15 @@ ftms = {
   };
 };
 
+ftms = {
+  name = formatters;
+  values = {
+    v = 202302;
+    cxxmin = 23;
+    hosted = yes;
+  };
+};
+
 ftms = {
   name = ios_noreplace;
   values = {
diff --git a/libstdc++-v3/include/std/stacktrace b/libstdc++-v3/include/std/stacktrace
index 358a81b82e5..da0e48d3532 100644
--- a/libstdc++-v3/include/std/stacktrace
+++ b/libstdc++-v3/include/std/stacktrace
@@ -31,10 +31,12 @@
 #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
diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread
index 28582c9df5c..c182a4d56c1 100644
--- a/libstdc++-v3/include/std/thread
+++ b/libstdc++-v3/include/std/thread
@@ -42,10 +42,15 @@
 # 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
index 00000000000..5116413344d
--- /dev/null
+++ b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/output.cc
@@ -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();
+}
diff --git a/libstdc++-v3/testsuite/19_diagnostics/stacktrace/synopsis.cc b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/synopsis.cc
index 262abea21ec..ece5d526fb9 100644
--- a/libstdc++-v3/testsuite/19_diagnostics/stacktrace/synopsis.cc
+++ b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/synopsis.cc
@@ -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>>;
   }
diff --git a/libstdc++-v3/testsuite/19_diagnostics/stacktrace/version.cc b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/version.cc
index ed466be5a36..d59a0696c25 100644
--- a/libstdc++-v3/testsuite/19_diagnostics/stacktrace/version.cc
+++ b/libstdc++-v3/testsuite/19_diagnostics/stacktrace/version.cc
@@ -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
diff --git a/libstdc++-v3/testsuite/30_threads/thread/id/hash.cc b/libstdc++-v3/testsuite/30_threads/thread/id/hash.cc
index 6af80e5c730..afbae8f2b90 100644
--- a/libstdc++-v3/testsuite/30_threads/thread/id/hash.cc
+++ b/libstdc++-v3/testsuite/30_threads/thread/id/hash.cc
@@ -1,6 +1,4 @@
 // { dg-do compile { target c++11 } }
-// { dg-require-cstdint "" }
-// { dg-require-gthreads "" }
 
 // Copyright (C) 2010-2023 Free Software Foundation, Inc.
 //
diff --git a/libstdc++-v3/testsuite/30_threads/thread/id/operators.cc b/libstdc++-v3/testsuite/30_threads/thread/id/operators.cc
index 88ec17c33be..c47a9e262b4 100644
--- a/libstdc++-v3/testsuite/30_threads/thread/id/operators.cc
+++ b/libstdc++-v3/testsuite/30_threads/thread/id/operators.cc
@@ -1,5 +1,4 @@
 // { dg-do compile { target c++11 } }
-// { dg-require-gthreads "" }
 
 // Copyright (C) 2009-2023 Free Software Foundation, Inc.
 //
diff --git a/libstdc++-v3/testsuite/30_threads/thread/id/operators_c++20.cc b/libstdc++-v3/testsuite/30_threads/thread/id/operators_c++20.cc
index 9432ec3468f..667b6a3dcf5 100644
--- a/libstdc++-v3/testsuite/30_threads/thread/id/operators_c++20.cc
+++ b/libstdc++-v3/testsuite/30_threads/thread/id/operators_c++20.cc
@@ -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
index 00000000000..08d8c899fda
--- /dev/null
+++ b/libstdc++-v3/testsuite/30_threads/thread/id/output.cc
@@ -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();
+}
-- 
2.41.0



More information about the Libstdc++ mailing list