From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id AA151385701E for ; Mon, 11 Sep 2023 13:57:47 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org AA151385701E Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1694440667; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=5Drb6qcx7ilVXZ0lXROyowp/xhQmnLF5cwJE5Di/B9o=; b=ZCDKVx5tSYQNns4QerpHu92KsIR2ZqjQO7JTu7++cEhTht9A1us8HFvWSfit5K55BqNIDL 3Y2jr8ztR/+t3YkNTWwbWQe4GP+KOlWjgker9qiwWOMg843KIsRE6i6pWa64A+SBgPz/C4 IjoMr0RcDw8G1SqFSHRkD9S2Mb2hMM0= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-38-0aSxPKa9OPaGe85wTrHsqw-1; Mon, 11 Sep 2023 09:57:43 -0400 X-MC-Unique: 0aSxPKa9OPaGe85wTrHsqw-1 Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.rdu2.redhat.com [10.11.54.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 13FE9101FAA6; Mon, 11 Sep 2023 13:57:43 +0000 (UTC) Received: from localhost (unknown [10.42.28.190]) by smtp.corp.redhat.com (Postfix) with ESMTP id AB53640C2064; Mon, 11 Sep 2023 13:57:42 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Formatting std::thread::id and std::stacktrace (P2693R1) Date: Mon, 11 Sep 2023 14:57:08 +0100 Message-ID: <20230911135742.1870920-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.1 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-11.0 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE,TXREP,URIBL_BLACK autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: 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) (formatter>): Define. * include/std/thread (formatter): 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 #define __glibcxx_want_stacktrace +#define __glibcxx_want_formatters #include #ifdef __cpp_lib_stacktrace // C++ >= 23 && hosted && HAVE_STACKTRACE #include +#include #include #include #include @@ -692,6 +694,84 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return std::move(__os).str(); } + template<> + class formatter + { + public: + constexpr typename basic_format_parse_context::iterator + parse(basic_format_parse_context& __pc) + { + __format::_Spec __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 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 _M_spec; + }; + + template + class formatter> + { + public: + constexpr typename basic_format_parse_context::iterator + parse(basic_format_parse_context& __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 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 // std::stop_source, std::stop_token, std::nostopstate #endif +#if __cplusplus >= 202302L +# include +#endif + #include // std::thread, get_id, yield #include // std::this_thread::sleep_for, sleep_until #define __glibcxx_want_jthread +#define __glibcxx_want_formatters #include namespace std _GLIBCXX_VISIBILITY(default) @@ -281,6 +286,63 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; #endif // __cpp_lib_jthread +#ifdef __cpp_lib_formatters // C++ >= 23 + + template + class formatter + { + 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 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 +#include +#include + +#ifndef __cpp_lib_formatters +# error "Feature-test macro for formatters missing in " +#elif __cpp_lib_formatters < 202302L +# error "Feature-test macro for formatters has wrong value in " +#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> ); + static_assert( std::is_default_constructible_v> ); + static_assert( std::is_default_constructible_v> ); + + 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& st); + template<> struct formatter; + template struct formatter>; + namespace pmr { using stacktrace = basic_stacktrace>; } 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 " #endif + +#ifndef __cpp_lib_formatters +# error "Feature-test macro for formatters missing in " +#elif __cpp_lib_formatters < 202302L +# error "Feature-test macro for formatters has wrong value in " +#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 +#include +#include +#include + +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::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> ); + 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 " +#endif +} + +int main() +{ + test01(); + test02(); +} -- 2.41.0