diff --git a/libcxx/include/__thread/thread.h b/libcxx/include/__thread/thread.h --- a/libcxx/include/__thread/thread.h +++ b/libcxx/include/__thread/thread.h @@ -22,6 +22,10 @@ #include <__utility/forward.h> #include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +# include +#endif + #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header #endif @@ -124,11 +128,66 @@ } }; -template -_LIBCPP_INLINE_VISIBILITY -basic_ostream<_CharT, _Traits>& -operator<<(basic_ostream<_CharT, _Traits>& __os, __thread_id __id) -{return __os << __id.__id_;} +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +template +_LIBCPP_INLINE_VISIBILITY basic_ostream<_CharT, _Traits>& +operator<<(basic_ostream<_CharT, _Traits>& __os, __thread_id __id) { + // [thread.thread.id]/9 + // Effects: Inserts the text representation for charT of id into out. + // + // [thread.thread.id]/2 + // The text representation for the character type charT of an + // object of type thread::id is an unspecified sequence of charT + // such that, for two objects of type thread::id x and y, if + // x == y is true, the thread::id objects have the same text + // representation, and if x != y is true, the thread::id objects + // have distinct text representations. + // + // In libc++ the thread::id is either an integral or a pointer. + // The wording only acknowledges a text representation, which + // means the formatting flags for integrals and pointers may not + // affect the text representation. It does not mention that the data + // in the stream must be identical for the same thread. So fill + // align and width are unaffected can affect the data in the stream. + // + // Note this wording it the wording of C++23, which changed due to + // the formatter thread::id, however earlier versions of C++ + // effectively had the same wording. + + static_assert(is_integral::value || is_pointer::value, + "Validate that for the new type the formatting flags are properly guarded, specifically the " + "precision is not safe yet."); + + // Per Table 124: basic_ios::init() effects [tab:basic.ios.cons] flags() + ios_base::fmtflags __default = ios_base::skipws | ios_base::dec; + + ios_base::fmtflags __flags = __os.flags(); + + // The internal alignment may affect the value when the value is + // negative + // Table 114: Fill padding [tab:facet.num.put.fill] + // adjustfield == internal and a sign occurs in the representation + // pad after the sign + // + // For a sequence of char + // [ostream.formatted.reqmts]/3 + // If a formatted output function of a stream os determines + // padding, it does so as follows. Given a charT character + // sequence seq where charT is the character type of the stream, + // if the length of seq is less than os.width(), then enough + // copies of os.fill() are added to this sequence as necessary to + // pad to a width of os.width() characters. If (os.flags() & + // ios_base::adjustfield) == ios_base::left is true, the fill + // characters are placed after the character sequence; otherwise, + // they are placed before the character sequence + + __os.flags(__default | ((__flags & ios_base::left) ? ios_base::left : ios_base::right)); + __os << __id.__id_; + + __os.flags(__flags); + return __os; +} +#endif // _LIBCPP_HAS_NO_LOCALIZATION class _LIBCPP_EXPORTED_FROM_ABI thread { diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -854,6 +854,7 @@ thread cstring thread ctime thread functional +thread ios thread iosfwd thread limits thread locale diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -855,6 +855,7 @@ thread cstring thread ctime thread functional +thread ios thread iosfwd thread limits thread locale diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -857,6 +857,7 @@ thread cstring thread ctime thread functional +thread ios thread iosfwd thread limits thread locale diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -857,6 +857,7 @@ thread cstring thread ctime thread functional +thread ios thread iosfwd thread limits thread locale diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -862,6 +862,7 @@ thread cstring thread ctime thread functional +thread ios thread iosfwd thread limits thread locale diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -589,6 +589,7 @@ thread cstdlib thread ctime thread initializer_list +thread ios thread iosfwd thread limits thread locale diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -589,6 +589,7 @@ thread cstdlib thread ctime thread initializer_list +thread ios thread iosfwd thread limits thread locale diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp --- a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp +++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/stream.pass.cpp @@ -30,7 +30,7 @@ #include "test_macros.h" template -static void test() { +static void basic() { std::thread::id id0 = std::this_thread::get_id(); std::basic_ostringstream os; os << id0; @@ -49,6 +49,82 @@ #endif } +template +static std::basic_string format(std::ios_base::fmtflags flags) { + std::basic_stringstream sstr; + sstr.flags(flags); + sstr << std::this_thread::get_id(); + return sstr.str(); +} + +template +static void stream_state() { + std::basic_stringstream sstr; + sstr << std::this_thread::get_id(); + std::basic_string expected = sstr.str(); + + // Unaffected by fill, width, and align. + + assert(expected == format(std::ios_base::dec | std::ios_base::skipws)); // default flags + + assert(expected == format(std::ios_base::oct)); + assert(expected == format(std::ios_base::hex)); + + assert(expected == format(std::ios_base::scientific)); + assert(expected == format(std::ios_base::fixed)); + + assert(expected == format(std::ios_base::boolalpha)); + assert(expected == format(std::ios_base::showbase)); + assert(expected == format(std::ios_base::showpoint)); + assert(expected == format(std::ios_base::showpos)); + assert(expected == format(std::ios_base::skipws)); // added for completion + assert(expected == format(std::ios_base::unitbuf)); // added for completion + assert(expected == format(std::ios_base::uppercase)); + + // Test fill, width, and align. + + sstr.str(std::basic_string()); + sstr.fill(CharT('#')); + sstr.width(expected.size() + 10); // Make sure fill and align affect the output. + sstr.flags(std::ios_base::dec | std::ios_base::skipws | std::ios_base::right); + sstr << std::this_thread::get_id(); + expected = sstr.str(); + + sstr.str(std::basic_string()); + sstr.fill(CharT('*')); + sstr.width(expected.size()); + sstr.flags(std::ios_base::dec | std::ios_base::skipws | std::ios_base::right); + sstr << std::this_thread::get_id(); + assert(expected != sstr.str()); + + sstr.str(std::basic_string()); + sstr.fill(CharT('#')); + sstr.width(expected.size() - 1); + sstr.flags(std::ios_base::dec | std::ios_base::skipws | std::ios_base::right); + sstr << std::this_thread::get_id(); + assert(expected != sstr.str()); + + sstr.str(std::basic_string()); + sstr.fill(CharT('#')); + sstr.width(expected.size()); + sstr.flags(std::ios_base::dec | std::ios_base::skipws | std::ios_base::left); + sstr << std::this_thread::get_id(); + assert(expected != sstr.str()); + + sstr.str(std::basic_string()); + sstr.fill(CharT('#')); + sstr.width(expected.size()); + sstr.flags(std::ios_base::dec | std::ios_base::skipws | std::ios_base::internal); + sstr << std::this_thread::get_id(); + assert(expected == sstr.str()); // internal does *not* affect strings +} + +template +static void test() { + basic(); + stream_state(); +} + int main(int, char**) { test(); #ifndef TEST_HAS_NO_WIDE_CHARACTERS