[libcxx-commits] [libcxx] 7cc72a0 - Implement syncstream (p0053)
Mark de Wever via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Nov 8 08:45:26 PST 2023
Author: Mark de Wever
Date: 2023-11-08T17:45:06+01:00
New Revision: 7cc72a0a2ec22855572d96411febd4f2c4ac5a49
URL: https://github.com/llvm/llvm-project/commit/7cc72a0a2ec22855572d96411febd4f2c4ac5a49
DIFF: https://github.com/llvm/llvm-project/commit/7cc72a0a2ec22855572d96411febd4f2c4ac5a49.diff
LOG: Implement syncstream (p0053)
This patch implements `std::basic_syncbuf` and `std::basic_osyncstream` as specified in paper p0053r7. ~~For ease of reviewing I am submitting this patch before submitting a patch for `std::basic_osyncstream`. ~~
~~Please note, this patch is not 100% complete. I plan on adding more tests (see comments), specifically I plan on adding tests for multithreading and synchronization.~~
Edit: I decided that it would be far easier for me to keep track of this and make changes that affect both `std::basic_syncbuf` and `std::basic_osyncstream` if both were in one patch.
The patch was originally written by @zoecarver
Implements
- P0053R7 - C++ Synchronized Buffered Ostream
- LWG-3127 basic_osyncstream::rdbuf needs a const_cast
- LWG-3334 basic_osyncstream move assignment and destruction calls basic_syncbuf::emit() twice
- LWG-3570 basic_osyncstream::emit should be an unformatted output function
- LWG-3867 Should std::basic_osyncstream's move assignment operator be noexcept?
Reviewed By: ldionne, #libc
Differential Revision: https://reviews.llvm.org/D67086
Added:
libcxx/include/syncstream
libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp
libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/helpers.h
libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp
libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp
libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp
Modified:
libcxx/docs/FeatureTestMacroTable.rst
libcxx/docs/ReleaseNotes/18.rst
libcxx/docs/Status/Cxx20Issues.csv
libcxx/docs/Status/Cxx20Papers.csv
libcxx/docs/Status/Cxx23Issues.csv
libcxx/include/CMakeLists.txt
libcxx/include/__config
libcxx/include/__std_clang_module
libcxx/include/iosfwd
libcxx/include/map
libcxx/include/streambuf
libcxx/include/version
libcxx/modules/std.cppm.in
libcxx/modules/std/iosfwd.inc
libcxx/modules/std/syncstream.inc
libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
libcxx/test/libcxx/transitive_includes/cxx03.csv
libcxx/test/libcxx/transitive_includes/cxx11.csv
libcxx/test/libcxx/transitive_includes/cxx14.csv
libcxx/test/libcxx/transitive_includes/cxx17.csv
libcxx/test/libcxx/transitive_includes/cxx20.csv
libcxx/test/libcxx/transitive_includes/cxx23.csv
libcxx/test/libcxx/transitive_includes/cxx26.csv
libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
libcxx/test/support/test_allocator.h
libcxx/utils/generate_feature_test_macro_components.py
libcxx/utils/libcxx/header_information.py
libcxx/utils/libcxx/test/params.py
Removed:
################################################################################
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 10afd64e0a39469..42bf6eb46212d07 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -286,7 +286,7 @@ Status
--------------------------------------------------- -----------------
``__cpp_lib_string_view`` ``201803L``
--------------------------------------------------- -----------------
- ``__cpp_lib_syncbuf`` *unimplemented*
+ ``__cpp_lib_syncbuf`` ``201803L``
--------------------------------------------------- -----------------
``__cpp_lib_three_way_comparison`` *unimplemented*
--------------------------------------------------- -----------------
diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index 41c3f7cc455f185..cf70d45e97886df 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -47,6 +47,7 @@ Implemented Papers
- P2443R1 - ``views::chunk_by``
- P2538R1 - ADL-proof ``std::projected``
- P2614R2 - Deprecate ``numeric_limits::has_denorm``
+- P0053R7 - C++ Synchronized Buffered Ostream (in the experimental library)
Improvements and New Features
diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index 6d27e2406fb0e89..08b8bda9705e0d3 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -114,7 +114,7 @@
"`3096 <https://wg21.link/LWG3096>`__","``path::lexically_relative``\ is confused by trailing slashes","San Diego","|Complete|",""
"`3116 <https://wg21.link/LWG3116>`__","``OUTERMOST_ALLOC_TRAITS``\ needs ``remove_reference_t``\ ","San Diego","",""
"`3122 <https://wg21.link/LWG3122>`__","``__cpp_lib_chrono_udls``\ was accidentally dropped","San Diego","|Complete|",""
-"`3127 <https://wg21.link/LWG3127>`__","``basic_osyncstream::rdbuf``\ needs a ``const_cast``\ ","San Diego","",""
+"`3127 <https://wg21.link/LWG3127>`__","``basic_osyncstream::rdbuf``\ needs a ``const_cast``\ ","San Diego","|Complete|","18.0"
"`3128 <https://wg21.link/LWG3128>`__","``strstream::rdbuf``\ needs a ``const_cast``\ ","San Diego","|Nothing To Do|",""
"`3129 <https://wg21.link/LWG3129>`__","``regex_token_iterator``\ constructor uses wrong pointer arithmetic","San Diego","",""
"`3130 <https://wg21.link/LWG3130>`__","|sect|\ [input.output] needs many ``addressof``\ ","San Diego","",""
@@ -254,7 +254,7 @@
"`3330 <https://wg21.link/LWG3330>`__","Include ``<compare>``\ from most library headers","Prague","|Complete|","13.0","|spaceship|"
"`3331 <https://wg21.link/LWG3331>`__","Define ``totally_ordered/_with``\ in terms of ``partially-ordered-with``\ ","Prague","|Complete|","13.0"
"`3332 <https://wg21.link/LWG3332>`__","Issue in |sect|\ [time.format]","Prague","|Complete|","16.0","|chrono| |format|"
-"`3334 <https://wg21.link/LWG3334>`__","``basic_osyncstream``\ move assignment and destruction calls ``basic_syncbuf::emit()``\ twice","Prague","",""
+"`3334 <https://wg21.link/LWG3334>`__","``basic_osyncstream``\ move assignment and destruction calls ``basic_syncbuf::emit()``\ twice","Prague","|Complete|","18.0"
"`3335 <https://wg21.link/LWG3335>`__","Resolve C++20 NB comments US 273 and GB 274","Prague","|Complete|","15.0","|ranges|"
"`3338 <https://wg21.link/LWG3338>`__","Rename ``default_constructible``\ to ``default_initializable``\ ","Prague","|Complete|","13.0"
"`3340 <https://wg21.link/LWG3340>`__","Formatting functions should throw on argument/format string mismatch in |sect|\ [format.functions]","Prague","|Complete|","14.0","|format|"
diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv
index dd6fcf9a7583ce3..107b2bca110f692 100644
--- a/libcxx/docs/Status/Cxx20Papers.csv
+++ b/libcxx/docs/Status/Cxx20Papers.csv
@@ -3,7 +3,7 @@
"`P0674R1 <https://wg21.link/P0674R1>`__","LWG","Extending make_shared to Support Arrays","Toronto","|Complete|","15.0"
"","","","","","",""
"`P0020R6 <https://wg21.link/P0020R6>`__","LWG","Floating Point Atomic","Albuquerque","",""
-"`P0053R7 <https://wg21.link/P0053R7>`__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","",""
+"`P0053R7 <https://wg21.link/P0053R7>`__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","|Complete|","18.0"
"`P0202R3 <https://wg21.link/P0202R3>`__","LWG","Add constexpr modifiers to functions in <algorithm> and <utility> Headers","Albuquerque","|Complete|","12.0"
"`P0415R1 <https://wg21.link/P0415R1>`__","LWG","Constexpr for ``std::complex``\ ","Albuquerque","|Complete|","16.0"
"`P0439R0 <https://wg21.link/P0439R0>`__","LWG","Make ``std::memory_order``\ a scoped enumeration","Albuquerque","|Complete|",""
diff --git a/libcxx/docs/Status/Cxx23Issues.csv b/libcxx/docs/Status/Cxx23Issues.csv
index 8be1ff588cf3b5c..fbe48123c1febf0 100644
--- a/libcxx/docs/Status/Cxx23Issues.csv
+++ b/libcxx/docs/Status/Cxx23Issues.csv
@@ -122,7 +122,7 @@
`3566 <https://wg21.link/LWG3566>`__,"Constraint recursion for ``operator<=>(optional<T>, U)``","October 2021","|Complete|","17.0","|spaceship|"
`3567 <https://wg21.link/LWG3567>`__,"Formatting move-only iterators take two","October 2021","|Complete|","16.0","|format| |ranges|"
`3568 <https://wg21.link/LWG3568>`__,"``basic_istream_view`` needs to initialize ``value_``","October 2021","|Complete|","16.0","|ranges|"
-`3570 <https://wg21.link/LWG3570>`__,"``basic_osyncstream::emit`` should be an unformatted output function","October 2021","",""
+`3570 <https://wg21.link/LWG3570>`__,"``basic_osyncstream::emit`` should be an unformatted output function","October 2021","|Complete|","18.0"
`3571 <https://wg21.link/LWG3571>`__,"``flush_emit`` should set ``badbit`` if the ``emit`` call fails","October 2021","",""
`3572 <https://wg21.link/LWG3572>`__,"``copyable-box`` should be fully ``constexpr``","October 2021","|Complete|","14.0","|ranges|"
`3573 <https://wg21.link/LWG3573>`__,"Missing Throws element for ``basic_string_view(It begin, End end)``","October 2021","|Complete|","14.0"
@@ -275,7 +275,7 @@
"`3857 <https://wg21.link/LWG3857>`__","``basic_string_view`` should allow explicit conversion when only traits vary","February 2023","|Complete|","17.0",""
"`3860 <https://wg21.link/LWG3860>`__","``range_common_reference_t`` is missing","February 2023","|Complete|","17.0","|ranges|"
"`3866 <https://wg21.link/LWG3866>`__","Bad Mandates for ``expected::transform_error`` overloads","February 2023","|Complete|","17.0",""
-"`3867 <https://wg21.link/LWG3867>`__","Should ``std::basic_osyncstream``'s move assignment operator be ``noexcept``?","February 2023","","",""
+"`3867 <https://wg21.link/LWG3867>`__","Should ``std::basic_osyncstream``'s move assignment operator be ``noexcept``?","February 2023","|Complete|","18.0",""
"`3441 <https://wg21.link/LWG3441>`__","Misleading note about calls to customization points","February 2023","","",""
"`3622 <https://wg21.link/LWG3622>`__","Misspecified transitivity of equivalence in ยง[unord.req.general]","February 2023","","",""
"`3631 <https://wg21.link/LWG3631>`__","``basic_format_arg(T&&)`` should use ``remove_cvref_t<T>`` throughout","February 2023","|Complete|","17.0",""
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 5e8c1700ee4cdad..889d7fedbf2965f 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -1000,6 +1000,7 @@ set(files
string.h
string_view
strstream
+ syncstream
system_error
tgmath.h
thread
diff --git a/libcxx/include/__config b/libcxx/include/__config
index b9b20fbfb271e8e..28802be542e446c 100644
--- a/libcxx/include/__config
+++ b/libcxx/include/__config
@@ -452,6 +452,7 @@
# define _LIBCPP_HAS_NO_INCOMPLETE_PSTL
# define _LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN
# define _LIBCPP_HAS_NO_INCOMPLETE_TZDB
+# define _LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM
# endif
// Need to detect which libc we're using if we're on Linux.
diff --git a/libcxx/include/__std_clang_module b/libcxx/include/__std_clang_module
index c4bfe8259c74a3a..10ac3ccb8f329f4 100644
--- a/libcxx/include/__std_clang_module
+++ b/libcxx/include/__std_clang_module
@@ -200,6 +200,9 @@
#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
# include <strstream>
#endif
+#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+# include <syncstream>
+#endif
#include <system_error>
#include <tgmath.h>
#if !defined(_LIBCPP_HAS_NO_THREADS)
diff --git a/libcxx/include/iosfwd b/libcxx/include/iosfwd
index dbbf7fdaf6de4b2..80c0c27179b8f1e 100644
--- a/libcxx/include/iosfwd
+++ b/libcxx/include/iosfwd
@@ -90,6 +90,18 @@ using u8streampos = fpos<char_traits<char8_t>::state_type>; // C++20
using u16streampos = fpos<char_traits<char16_t>::state_type>;
using u32streampos = fpos<char_traits<char32_t>::state_type>;
+template <class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+ class basic_syncbuf; // C++20
+
+using syncbuf = basic_syncbuf<char>; // C++20
+using wsyncbuf = basic_syncbuf<wchar_t>; // C++20
+
+template <class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+ class basic_osyncstream; // C++20
+
+using osyncstream = basic_osyncstream<char>; // C++20
+using wosyncstream = basic_osyncstream<wchar_t>; // C++20
+
} // std
*/
@@ -130,6 +142,26 @@ typedef fpos<mbstate_t> u8streampos;
typedef fpos<mbstate_t> u16streampos;
typedef fpos<mbstate_t> u32streampos;
+#if _LIBCPP_STD_VER >= 20
+
+template <class _CharT, class _Traits = char_traits<_CharT>, class _Allocator = allocator<_CharT>>
+class basic_syncbuf;
+
+using syncbuf = basic_syncbuf<char>;
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using wsyncbuf = basic_syncbuf<wchar_t>;
+#endif
+
+template <class _CharT, class _Traits = char_traits<_CharT>, class _Allocator = allocator<_CharT>>
+class basic_osyncstream;
+
+using osyncstream = basic_osyncstream<char>;
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using wosyncstream = basic_osyncstream<wchar_t>;
+#endif
+
+#endif // _LIBCPP_STD_VER >=20
+
// Include other forward declarations here
template <class _Tp, class _Alloc = allocator<_Tp> >
class _LIBCPP_TEMPLATE_VIS vector;
diff --git a/libcxx/include/map b/libcxx/include/map
index 04cf2d313422327..1b0f1d1c792b90e 100644
--- a/libcxx/include/map
+++ b/libcxx/include/map
@@ -1700,6 +1700,8 @@ template <class _Key, class _Tp, class _Compare, class _Allocator>
_Tp&
map<_Key, _Tp, _Compare, _Allocator>::operator[](key_type&& __k)
{
+ // TODO investigate this clang-tidy warning.
+ // NOLINTNEXTLINE(bugprone-use-after-move)
return __tree_.__emplace_unique_key_args(__k,
_VSTD::piecewise_construct,
_VSTD::forward_as_tuple(_VSTD::move(__k)),
diff --git a/libcxx/include/streambuf b/libcxx/include/streambuf
index 9d94e42f37fe20e..6d6fd143988204e 100644
--- a/libcxx/include/streambuf
+++ b/libcxx/include/streambuf
@@ -110,6 +110,7 @@ protected:
#include <__assert> // all public C++ headers provide the assertion handler
#include <__config>
#include <__fwd/streambuf.h>
+#include <climits>
#include <ios>
#include <iosfwd>
#include <version>
diff --git a/libcxx/include/syncstream b/libcxx/include/syncstream
new file mode 100644
index 000000000000000..c54e8ce9f54ce47
--- /dev/null
+++ b/libcxx/include/syncstream
@@ -0,0 +1,510 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP_SYNCSTREAM
+#define _LIBCPP_SYNCSTREAM
+
+/*
+ syncstream synopsis
+
+#include <ostream> // see [ostream.syn]
+
+namespace std {
+ template<class charT, class traits, class Allocator>
+ class basic_syncbuf;
+
+ // [syncstream.syncbuf.special], specialized algorithms
+ template<class charT, class traits, class Allocator>
+ void swap(basic_syncbuf<charT, traits, Allocator>&,
+ basic_syncbuf<charT, traits, Allocator>&);
+
+ using syncbuf = basic_syncbuf<char>;
+ using wsyncbuf = basic_syncbuf<wchar_t>;
+
+ template<class charT, class traits, class Allocator>
+ class basic_osyncstream;
+
+ using osyncstream = basic_osyncstream<char>;
+ using wosyncstream = basic_osyncstream<wchar_t>;
+
+ template<class charT, class traits, class Allocator>
+ class basic_syncbuf : public basic_streambuf<charT, traits> {
+ public:
+ using char_type = charT;
+ using int_type = typename traits::int_type;
+ using pos_type = typename traits::pos_type;
+ using off_type = typename traits::off_type;
+ using traits_type = traits;
+ using allocator_type = Allocator;
+
+ using streambuf_type = basic_streambuf<charT, traits>;
+
+ // [syncstream.syncbuf.cons], construction and destruction
+ explicit basic_syncbuf(streambuf_type* obuf = nullptr)
+ : basic_syncbuf(obuf, Allocator()) {}
+ basic_syncbuf(streambuf_type*, const Allocator&);
+ basic_syncbuf(basic_syncbuf&&);
+ ~basic_syncbuf();
+
+ // [syncstream.syncbuf.assign], assignment and swap
+ basic_syncbuf& operator=(basic_syncbuf&&);
+ void swap(basic_syncbuf&);
+
+ // [syncstream.syncbuf.members], member functions
+ bool emit();
+ streambuf_type* get_wrapped() const noexcept;
+ allocator_type get_allocator() const noexcept;
+ void set_emit_on_sync(bool) noexcept;
+
+ protected:
+ // [syncstream.syncbuf.virtuals], overridden virtual functions
+ int sync() override;
+
+ private:
+ streambuf_type* wrapped; // exposition only
+ bool emit_on_sync{}; // exposition only
+ };
+
+ // [syncstream.syncbuf.special], specialized algorithms
+ template<class charT, class traits, class Allocator>
+ void swap(basic_syncbuf<charT, traits, Allocator>&,
+ basic_syncbuf<charT, traits, Allocator>&);
+
+ template<class charT, class traits, class Allocator>
+ class basic_osyncstream : public basic_ostream<charT, traits> {
+ public:
+ using char_type = charT;
+ using int_type = typename traits::int_type;
+ using pos_type = typename traits::pos_type;
+ using off_type = typename traits::off_type;
+ using traits_type = traits;
+
+ using allocator_type = Allocator;
+ using streambuf_type = basic_streambuf<charT, traits>;
+ using syncbuf_type = basic_syncbuf<charT, traits, Allocator>;
+
+ // [syncstream.osyncstream.cons], construction and destruction
+ basic_osyncstream(streambuf_type*, const Allocator&);
+ explicit basic_osyncstream(streambuf_type* obuf)
+ : basic_osyncstream(obuf, Allocator()) {}
+ basic_osyncstream(basic_ostream<charT, traits>& os, const Allocator& allocator)
+ : basic_osyncstream(os.rdbuf(), allocator) {}
+ explicit basic_osyncstream(basic_ostream<charT, traits>& os)
+ : basic_osyncstream(os, Allocator()) {}
+ basic_osyncstream(basic_osyncstream&&) noexcept;
+ ~basic_osyncstream();
+
+ // [syncstream.osyncstream.assign], assignment
+ basic_osyncstream& operator=(basic_osyncstream&&);
+
+ // [syncstream.osyncstream.members], member functions
+ void emit();
+ streambuf_type* get_wrapped() const noexcept;
+ syncbuf_type* rdbuf() const noexcept { return const_cast<syncbuf_type*>(addressof(sb)); }
+
+ private:
+ syncbuf_type sb; // exposition only
+ };
+}
+
+*/
+
+#include <__config>
+#include <__utility/move.h>
+#include <iosfwd> // required for declaration of default arguments
+#include <string>
+
+#ifndef _LIBCPP_HAS_NO_THREADS
+# include <map>
+# include <mutex>
+# include <shared_mutex>
+#endif
+
+// standard-mandated includes
+
+// [syncstream.syn]
+#include <ostream>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+
+// [syncstream.syncbuf.overview]/1
+// Class template basic_syncbuf stores character data written to it,
+// known as the associated output, into internal buffers allocated
+// using the object's allocator. The associated output is transferred
+// to the wrapped stream buffer object *wrapped when emit() is called
+// or when the basic_syncbuf object is destroyed. Such transfers are
+// atomic with respect to transfers by other basic_syncbuf objects
+// with the same wrapped stream buffer object.
+//
+// This helper singleton is used to implement the required
+// synchronisation guarantees.
+# ifndef _LIBCPP_HAS_NO_THREADS
+class __wrapped_streambuf_mutex {
+ _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex() = default;
+
+public:
+ __wrapped_streambuf_mutex(const __wrapped_streambuf_mutex&) = delete;
+ __wrapped_streambuf_mutex& operator=(const __wrapped_streambuf_mutex&) = delete;
+
+ _LIBCPP_HIDE_FROM_ABI void __inc_reference([[maybe_unused]] void* __ptr) {
+ _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
+ unique_lock __lock{__mutex_};
+ ++__lut_[reinterpret_cast<uintptr_t>(__ptr)].__count;
+ }
+
+ // pre: __ptr is in __lut_
+ _LIBCPP_HIDE_FROM_ABI void __dec_reference([[maybe_unused]] void* __ptr) noexcept {
+ unique_lock __lock{__mutex_};
+
+ auto __it = __get_it(__ptr);
+ if (__it->second.__count == 1)
+ __lut_.erase(__it);
+ else
+ --__it->second.__count;
+ }
+
+ // TODO
+ // This function causes emit() aquire two mutexes:
+ // - __mutex_ shared
+ // _ __get_it(__ptr)->second.__mutex exclusive
+ //
+ // Instead store a pointer to __get_it(__ptr)->second.__mutex when
+ // calling __inc_reference.
+ //
+ // pre: __ptr is in __lut_
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI lock_guard<mutex> __get_lock([[maybe_unused]] void* __ptr) noexcept {
+ shared_lock __lock{__mutex_};
+ return lock_guard{__get_it(__ptr)->second.__mutex};
+ }
+
+ // This function is used for testing.
+ //
+ // It is allowed to call this function with a non-registered pointer.
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t __get_count([[maybe_unused]] void* __ptr) noexcept {
+ _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
+ shared_lock __lock{__mutex_};
+
+ auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
+ return __it != __lut_.end() ? __it->second.__count : 0;
+ }
+
+ [[nodiscard]] static _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex& __instance() noexcept {
+ static __wrapped_streambuf_mutex __result;
+ return __result;
+ }
+
+private:
+ struct __value {
+ mutex __mutex;
+ size_t __count{0};
+ };
+
+ shared_mutex __mutex_;
+ map<uintptr_t, __value> __lut_;
+
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI map<uintptr_t, __value>::iterator __get_it(void* __ptr) noexcept {
+ _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
+
+ auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
+ _LIBCPP_ASSERT_INTERNAL(__it != __lut_.end(), "using a wrapped streambuf that has not been registered");
+ _LIBCPP_ASSERT_INTERNAL(__it->second.__count >= 1, "found an inactive streambuf wrapper");
+ return __it;
+ }
+};
+# endif // _LIBCPP_HAS_NO_THREADS
+
+// basic_syncbuf
+
+// The class uses a basic_string<_CharT, _Traits, _Allocator> as
+// internal buffer. Per [syncstream.syncbuf.cons]/4
+// Remarks: A copy of allocator is used to allocate memory for
+// internal buffers holding the associated output.
+//
+// Therefore the allocator used in the constructor is passed to the
+// basic_string. The class does not keep a copy of this allocator.
+template <class _CharT, class _Traits, class _Allocator>
+class _LIBCPP_TEMPLATE_VIS basic_syncbuf : public basic_streambuf<_CharT, _Traits> {
+public:
+ using char_type = _CharT;
+ using traits_type = _Traits;
+ using int_type = typename traits_type::int_type;
+ using pos_type = typename traits_type::pos_type;
+ using off_type = typename traits_type::off_type;
+ using allocator_type = _Allocator;
+
+ using streambuf_type = basic_streambuf<_CharT, _Traits>;
+
+ // [syncstream.syncbuf.cons], construction and destruction
+
+ _LIBCPP_HIDE_FROM_ABI explicit basic_syncbuf(streambuf_type* __obuf = nullptr)
+ : basic_syncbuf(__obuf, _Allocator()) {}
+
+ _LIBCPP_HIDE_FROM_ABI basic_syncbuf(streambuf_type* __obuf, _Allocator const& __alloc)
+ : __wrapped_(__obuf), __str_(__alloc) {
+ __inc_reference();
+ }
+
+ _LIBCPP_HIDE_FROM_ABI basic_syncbuf(basic_syncbuf&& __other)
+ : __wrapped_(__other.get_wrapped()), __str_(std::move(__other.__str_)), __emit_on_sync_(__other.__emit_on_sync_) {
+ __move_common(__other);
+ }
+
+ _LIBCPP_HIDE_FROM_ABI ~basic_syncbuf() {
+# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+ try {
+# endif // _LIBCPP_HAS_NO_EXCEPTIONS
+ emit();
+# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+ } catch (...) {
+ }
+# endif // _LIBCPP_HAS_NO_EXCEPTIONS
+ __dec_reference();
+ }
+
+ // [syncstream.syncbuf.assign], assignment and swap
+
+ _LIBCPP_HIDE_FROM_ABI basic_syncbuf& operator=(basic_syncbuf&& __other) {
+ // The function is specified to call emit. This call should
+ // propagate the exception thrown.
+ emit();
+ __dec_reference();
+
+ __wrapped_ = __other.get_wrapped();
+ __str_ = std::move(__other.__str_);
+ __emit_on_sync_ = __other.__emit_on_sync_;
+
+ __move_common(__other);
+
+ return *this;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI void swap(basic_syncbuf& __other) {
+ _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
+ allocator_traits<_Allocator>::propagate_on_container_swap::value || get_allocator() == __other.get_allocator(),
+ "violates the mandated swap precondition");
+
+ basic_syncbuf __tmp(std::move(__other));
+ __other = std::move(*this);
+ *this = std::move(__tmp);
+ }
+
+ // [syncstream.syncbuf.members], member functions
+
+ _LIBCPP_HIDE_FROM_ABI bool emit() { return emit(false); }
+
+ _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __wrapped_; }
+
+ _LIBCPP_HIDE_FROM_ABI allocator_type get_allocator() const noexcept { return __str_.get_allocator(); }
+
+ _LIBCPP_HIDE_FROM_ABI void set_emit_on_sync(bool __b) noexcept { __emit_on_sync_ = __b; }
+
+protected:
+ // [syncstream.syncbuf.virtuals], overridden virtual functions
+
+ _LIBCPP_HIDE_FROM_ABI_VIRTUAL
+ int sync() override {
+ if (__emit_on_sync_ && !emit(true))
+ return -1;
+ return 0;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI_VIRTUAL
+ int_type overflow(int_type __c = traits_type::eof()) override {
+ if (traits_type::eq_int_type(__c, traits_type::eof()))
+ return traits_type::not_eof(__c);
+
+ if (this->pptr() == this->epptr()) {
+# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+ try {
+# endif
+ size_t __size = __str_.size();
+ __str_.resize(__str_.capacity() + 1);
+ _LIBCPP_ASSERT_INTERNAL(__str_.size() > __size, "the buffer hasn't grown");
+
+ char_type* __p = static_cast<char_type*>(__str_.data());
+ this->setp(__p, __p + __str_.size());
+ this->pbump(__size);
+
+# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+ } catch (...) {
+ return traits_type::eof();
+ }
+# endif
+ }
+
+ return this->sputc(traits_type::to_char_type(__c));
+ }
+
+private:
+ streambuf_type* __wrapped_;
+
+ // TODO Use a more generic buffer.
+ // That buffer should be light with almost no additional headers. Then
+ // it can be use here, the __retarget_buffer, and place that use
+ // the now deprecated get_temporary_buffer
+
+ basic_string<_CharT, _Traits, _Allocator> __str_;
+ bool __emit_on_sync_{false};
+
+ _LIBCPP_HIDE_FROM_ABI bool emit(bool __flush) {
+ if (!__wrapped_)
+ return false;
+
+# ifndef _LIBCPP_HAS_NO_THREADS
+ lock_guard<mutex> __lock = __wrapped_streambuf_mutex::__instance().__get_lock(__wrapped_);
+# endif
+
+ bool __result = true;
+ if (this->pptr() != this->pbase()) {
+ _LIBCPP_ASSERT_INTERNAL(this->pbase() && this->pptr() && this->epptr(), "all put area pointers shold be valid");
+
+ // The __str_ does not know how much of its buffer is used. This
+ // information is extracted from the information of the base class.
+ __result &= (__wrapped_->sputn(this->pbase(), this->pptr() - this->pbase()) != -1);
+ // Clears the buffer, but keeps the contents (and) size of the
+ // internal buffer.
+ this->setp(this->pbase(), this->epptr());
+ }
+
+ if (__flush)
+ __result &= (__wrapped_->pubsync() != -1);
+
+ return __result;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI void __move_common(basic_syncbuf& __other) {
+ // Adjust the put area pointers to our buffer.
+ char_type* __p = static_cast<char_type*>(__str_.data());
+ this->setp(__p, __p + __str_.size());
+ this->pbump(__other.pptr() - __other.pbase());
+
+ // Clear __other_ so the destructor will act as a NOP.
+ __other.setp(nullptr, nullptr);
+ __other.__wrapped_ = nullptr;
+ }
+
+ _LIBCPP_HIDE_FROM_ABI void __inc_reference() {
+# ifndef _LIBCPP_HAS_NO_THREADS
+ if (__wrapped_)
+ __wrapped_streambuf_mutex::__instance().__inc_reference(__wrapped_);
+# endif
+ }
+
+ _LIBCPP_HIDE_FROM_ABI void __dec_reference() noexcept {
+# ifndef _LIBCPP_HAS_NO_THREADS
+ if (__wrapped_)
+ __wrapped_streambuf_mutex::__instance().__dec_reference(__wrapped_);
+# endif
+ }
+};
+
+using std::syncbuf;
+# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using std::wsyncbuf;
+# endif
+
+// [syncstream.syncbuf.special], specialized algorithms
+template <class _CharT, class _Traits, class _Allocator>
+_LIBCPP_HIDE_FROM_ABI void
+swap(basic_syncbuf<_CharT, _Traits, _Allocator>& __lhs, basic_syncbuf<_CharT, _Traits, _Allocator>& __rhs) {
+ __lhs.swap(__rhs);
+}
+
+// basic_osyncstream
+
+template <class _CharT, class _Traits, class _Allocator>
+class _LIBCPP_TEMPLATE_VIS basic_osyncstream : public basic_ostream<_CharT, _Traits> {
+public:
+ using char_type = _CharT;
+ using traits_type = _Traits;
+ using int_type = typename traits_type::int_type;
+ using pos_type = typename traits_type::pos_type;
+ using off_type = typename traits_type::off_type;
+
+ using allocator_type = _Allocator;
+ using streambuf_type = basic_streambuf<char_type, traits_type>;
+ using syncbuf_type = basic_syncbuf<char_type, traits_type, allocator_type>;
+
+ // [syncstream.osyncstream.cons], construction and destruction
+
+ _LIBCPP_HIDE_FROM_ABI basic_osyncstream(streambuf_type* __obuf, allocator_type const& __alloc)
+ : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(__obuf, __alloc) {}
+
+ _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(streambuf_type* __obuf)
+ : basic_osyncstream(__obuf, allocator_type()) {}
+
+ _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_ostream<char_type, traits_type>& __os, allocator_type const& __alloc)
+ : basic_osyncstream(__os.rdbuf(), __alloc) {}
+
+ _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(basic_ostream<char_type, traits_type>& __os)
+ : basic_osyncstream(__os, allocator_type()) {}
+
+ _LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_osyncstream&& __other) noexcept
+ : basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(std::move(__other.__sb_)) {
+ this->set_rdbuf(std::addressof(__sb_));
+ }
+
+ // [syncstream.osyncstream.assign], assignment
+
+ _LIBCPP_HIDE_FROM_ABI basic_osyncstream& operator=(basic_osyncstream&& __other) = default;
+
+ // [syncstream.osyncstream.members], member functions
+
+ _LIBCPP_HIDE_FROM_ABI void emit() {
+ // The basic_ostream::put places the sentry in a try
+ // catch, this does not match the wording of the standard
+ // [ostream.unformatted]
+ // TODO validate other unformatted output functions.
+ typename basic_ostream<char_type, traits_type>::sentry __s(*this);
+ if (__s) {
+# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+ try {
+# endif
+
+ if (__sb_.emit() == false)
+ this->setstate(ios::badbit);
+# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
+ } catch (...) {
+ this->__set_badbit_and_consider_rethrow();
+ }
+# endif
+ }
+ }
+
+ _LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __sb_.get_wrapped(); }
+
+ _LIBCPP_HIDE_FROM_ABI syncbuf_type* rdbuf() const noexcept {
+ return const_cast<syncbuf_type*>(std::addressof(__sb_));
+ }
+
+private:
+ syncbuf_type __sb_;
+};
+
+using std::osyncstream;
+# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+using std::wosyncstream;
+# endif
+
+#endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP_SYNCSTREAM
diff --git a/libcxx/include/version b/libcxx/include/version
index 3c8e1a132e87f85..ec0cb0813492383 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -401,7 +401,9 @@ __cpp_lib_within_lifetime 202306L <type_traits>
# define __cpp_lib_starts_ends_with 201711L
# undef __cpp_lib_string_view
# define __cpp_lib_string_view 201803L
-// # define __cpp_lib_syncbuf 201803L
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+# define __cpp_lib_syncbuf 201803L
+# endif
// # define __cpp_lib_three_way_comparison 201907L
# define __cpp_lib_to_address 201711L
# define __cpp_lib_to_array 201907L
diff --git a/libcxx/modules/std.cppm.in b/libcxx/modules/std.cppm.in
index 65cec804ff122ae..09ea531e172e131 100644
--- a/libcxx/modules/std.cppm.in
+++ b/libcxx/modules/std.cppm.in
@@ -148,6 +148,9 @@ module;
#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
# include <strstream>
#endif
+#if !defined(_LIBCPP_HAS_NO_LOCALIZATION)
+# include <syncstream>
+#endif
#include <system_error>
#if !defined(_LIBCPP_HAS_NO_THREADS)
# include <thread>
@@ -189,9 +192,6 @@ module;
#if __has_include(<stdfloat>)
# error "update the header information for <stdfloat> in libcxx/utils/generate_std_cppm_in.py"
#endif // __has_include(<stdfloat>)
-#if __has_include(<syncstream>)
-# error "update the header information for <syncstream> in libcxx/utils/generate_std_cppm_in.py"
-#endif // __has_include(<syncstream>)
#if __has_include(<text_encoding>)
# error "update the header information for <text_encoding> in libcxx/utils/generate_std_cppm_in.py"
#endif // __has_include(<text_encoding>)
diff --git a/libcxx/modules/std/iosfwd.inc b/libcxx/modules/std/iosfwd.inc
index aba7f1e419803e3..ec8b434ca0c51b7 100644
--- a/libcxx/modules/std/iosfwd.inc
+++ b/libcxx/modules/std/iosfwd.inc
@@ -16,8 +16,19 @@ export namespace std {
using std::u32streampos;
using std::u8streampos;
+ using std::basic_osyncstream;
+ using std::basic_syncbuf;
+
using std::istreambuf_iterator;
using std::ostreambuf_iterator;
+ using std::osyncstream;
+ using std::syncbuf;
+
+#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
+ using std::wosyncstream;
+ using std::wsyncbuf;
+#endif
+
using std::fpos;
} // namespace std
diff --git a/libcxx/modules/std/syncstream.inc b/libcxx/modules/std/syncstream.inc
index df9b679e38842b5..e3fb391b67c6364 100644
--- a/libcxx/modules/std/syncstream.inc
+++ b/libcxx/modules/std/syncstream.inc
@@ -8,7 +8,7 @@
//===----------------------------------------------------------------------===//
export namespace std {
-#if 0
+#if !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
using std::basic_syncbuf;
// [syncstream.syncbuf.special], specialized algorithms
@@ -24,5 +24,5 @@ export namespace std {
# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
using std::wosyncstream;
# endif
-#endif
+#endif // !defined(_LIBCPP_HAS_NO_LOCALIZATION) && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
} // namespace std
diff --git a/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp b/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
index 92062eb98889e0c..7c98ff1c1d566c6 100644
--- a/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
+++ b/libcxx/test/libcxx/experimental/fexperimental-library.compile.pass.cpp
@@ -27,3 +27,7 @@
#ifdef _LIBCPP_HAS_NO_INCOMPLETE_TZDB
# error "-fexperimental-library should enable the chrono TZDB"
#endif
+
+#ifdef _LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM
+# error "-fexperimental-library should enable the syncstream header"
+#endif
diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index 0e7523b5420d43d..65eae36a4c6c6e6 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -813,6 +813,7 @@ stop_token limits
stop_token ratio
stop_token type_traits
stop_token version
+streambuf climits
streambuf cstdint
streambuf ios
streambuf iosfwd
@@ -859,6 +860,13 @@ string_view version
strstream istream
strstream ostream
strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
system_error cerrno
system_error compare
system_error cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index 08e7786d9b19435..a6c574e1d4b64bd 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -819,6 +819,7 @@ stop_token limits
stop_token ratio
stop_token type_traits
stop_token version
+streambuf climits
streambuf cstdint
streambuf ios
streambuf iosfwd
@@ -865,6 +866,13 @@ string_view version
strstream istream
strstream ostream
strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
system_error cerrno
system_error compare
system_error cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 37a6e5e853d27e7..9021c91ee2aed05 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -821,6 +821,7 @@ stop_token limits
stop_token ratio
stop_token type_traits
stop_token version
+streambuf climits
streambuf cstdint
streambuf ios
streambuf iosfwd
@@ -867,6 +868,13 @@ string_view version
strstream istream
strstream ostream
strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
system_error cerrno
system_error compare
system_error cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 37a6e5e853d27e7..9021c91ee2aed05 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -821,6 +821,7 @@ stop_token limits
stop_token ratio
stop_token type_traits
stop_token version
+streambuf climits
streambuf cstdint
streambuf ios
streambuf iosfwd
@@ -867,6 +868,13 @@ string_view version
strstream istream
strstream ostream
strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
system_error cerrno
system_error compare
system_error cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index 5c9638fd467f214..c047702b445994e 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -826,6 +826,7 @@ stop_token limits
stop_token ratio
stop_token type_traits
stop_token version
+streambuf climits
streambuf cstdint
streambuf ios
streambuf iosfwd
@@ -872,6 +873,13 @@ string_view version
strstream istream
strstream ostream
strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
system_error cerrno
system_error compare
system_error cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index e1d489391c8de6c..a0ed2e290ffd92a 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -564,6 +564,7 @@ stop_token ctime
stop_token limits
stop_token ratio
stop_token version
+streambuf climits
streambuf ios
streambuf iosfwd
streambuf version
@@ -597,6 +598,13 @@ string_view version
strstream istream
strstream ostream
strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
system_error cerrno
system_error compare
system_error cstddef
diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index e1d489391c8de6c..a0ed2e290ffd92a 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -564,6 +564,7 @@ stop_token ctime
stop_token limits
stop_token ratio
stop_token version
+streambuf climits
streambuf ios
streambuf iosfwd
streambuf version
@@ -597,6 +598,13 @@ string_view version
strstream istream
strstream ostream
strstream version
+syncstream cstddef
+syncstream iosfwd
+syncstream map
+syncstream mutex
+syncstream ostream
+syncstream shared_mutex
+syncstream string
system_error cerrno
system_error compare
system_error cstddef
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp
new file mode 100644
index 000000000000000..14a9f10e24293f9
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream& operator=(basic_osyncstream&& rhs);
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "test_allocator.h"
+
+template <class CharT, bool propagate>
+static void test() {
+ using Traits = std::char_traits<CharT>;
+ using Allocator = std::conditional_t<propagate, other_allocator<CharT>, test_allocator<CharT>>;
+ static_assert(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value == propagate);
+
+ using OS = std::basic_osyncstream<CharT, Traits, Allocator>;
+
+ std::basic_stringbuf<CharT, Traits, Allocator> base1;
+ std::basic_stringbuf<CharT, Traits, Allocator> base2;
+
+ {
+ OS out1{&base1, Allocator{42}};
+ assert(out1.get_wrapped() == &base1);
+
+ typename OS::syncbuf_type* sb1 = out1.rdbuf();
+ assert(sb1->get_wrapped() == &base1);
+ assert(sb1->get_allocator().get_data() == 42);
+
+ out1 << CharT('A');
+
+ static_assert(!noexcept(out1.operator=(std::move(out1)))); // LWG-3867
+ OS out2{&base2, Allocator{99}};
+
+ out2 << CharT('Z');
+
+ // Validate the data is still in the syncbuf and not in the stringbuf.
+ assert(base1.str().empty());
+ assert(base2.str().empty());
+
+ out2 = std::move(out1);
+
+ // Since sb2 is overwritten by the move its data should be in its stringbuf.
+ assert(base1.str().empty());
+ assert(base2.str().size() == 1);
+ assert(base2.str()[0] == CharT('Z'));
+
+ assert(out2.get_wrapped() == &base1);
+
+ typename OS::syncbuf_type* sb2 = out2.rdbuf();
+ assert(sb2->get_wrapped() == &base1);
+ if constexpr (std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value)
+ assert(sb2->get_allocator().get_data() == 42);
+ else
+ assert(sb2->get_allocator().get_data() == 99);
+
+ assert(out1.get_wrapped() == nullptr);
+ assert(sb1->get_wrapped() == nullptr);
+
+ // The data written to 2 will be stored in sb1. The write happens after the destruction.
+ out2 << CharT('B');
+ assert(base1.str().empty());
+ }
+
+ assert(base1.str().size() == 2);
+ assert(base1.str()[0] == CharT('A'));
+ assert(base1.str()[1] == CharT('B'));
+ assert(base2.str().size() == 1);
+ assert(base2.str()[0] == CharT('Z'));
+}
+
+template <class CharT>
+static void test() {
+ test<CharT, true>();
+ test<CharT, false>();
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp
new file mode 100644
index 000000000000000..bf0c221b63ac33f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/members/emit.pass.cpp
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// void emit();
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+ using OS = std::basic_osyncstream<CharT>;
+ using SS = std::basic_ostringstream<CharT>;
+ CharT c = 'f';
+
+ SS ss;
+ OS out(ss);
+ out << c;
+ assert(ss.str().empty());
+ out.emit();
+ assert(ss.str()[0] == c);
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp
new file mode 100644
index 000000000000000..d57acf22302f672
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/members/get_wrapped.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// streambuf_type* get_wrapped() const noexcept;
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+ std::basic_stringbuf<CharT> base;
+ const std::basic_osyncstream<CharT> out{&base};
+ assert(out.get_wrapped() == &base);
+ ASSERT_NOEXCEPT(out.get_wrapped());
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp
new file mode 100644
index 000000000000000..975b6e1c4a1d20f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/members/rdbuf.pass.cpp
@@ -0,0 +1,40 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// syncbuf_type* rdbuf() const noexcept
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+ const std::basic_osyncstream<CharT> out{nullptr};
+ assert(out.rdbuf() != nullptr);
+ ASSERT_NOEXCEPT(out.rdbuf());
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp
new file mode 100644
index 000000000000000..7fcbe78e5a812ad
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.move.pass.cpp
@@ -0,0 +1,135 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream(basic_osyncstream&&) noexcept;
+
+// TODO Why is this noexcept?
+// Does the reasoning for https://cplusplus.github.io/LWG/issue3867 not hold true here?
+
+#include <cassert>
+#include <sstream>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using OS = std::basic_osyncstream<CharT>;
+ using W = std::basic_syncbuf<CharT>;
+
+ const std::allocator<CharT> alloc;
+ {
+ OS os = {OS{nullptr, alloc}};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ ASSERT_NOEXCEPT(OS{std::move(os)});
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os = {OS{&w, alloc}};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+ using W = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>>;
+
+ const std::allocator<CharT> alloc;
+ {
+ OS os = {OS{nullptr, alloc}};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os = {OS{&w, alloc}};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ using W = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+ const test_allocator<CharT> alloc;
+ {
+ OS os = {OS{nullptr, alloc}};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os = {OS{&w, alloc}};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp
new file mode 100644
index 000000000000000..271d2ad24e3cd19
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.allocator.pass.cpp
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream(basic_ostream& os, const Allocator& allocator);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using OS = std::basic_osyncstream<CharT>;
+ using W = std::basic_ostringstream<CharT>;
+
+ const std::allocator<CharT> alloc;
+
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ {
+ OS os = {w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+ assert(os.get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ }
+ }
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+ using W = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>>;
+
+ const std::allocator<CharT> alloc;
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ {
+ OS os = {w.rdbuf(), alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+ assert(os.get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ using W = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+ const test_allocator<CharT> alloc;
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ {
+ OS os = {w.rdbuf(), alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+ assert(os.get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp
new file mode 100644
index 000000000000000..411473c053d9b03
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.ostream.pass.cpp
@@ -0,0 +1,120 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// explicit basic_osyncstream(basic_ostream& os);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using OS = std::basic_osyncstream<CharT>;
+ using W = std::basic_ostringstream<CharT>;
+
+ static_assert(!std::convertible_to<std::basic_ostream<CharT>&, OS>);
+ static_assert(std::constructible_from<OS, std::basic_ostream<CharT>&>);
+
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ {
+ OS os{w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+ assert(os.get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+ using W = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>>;
+
+ static_assert(!std::convertible_to<std::basic_ostream<CharT, constexpr_char_traits<CharT>>&, OS>);
+ static_assert(std::constructible_from<OS, std::basic_ostream<CharT, constexpr_char_traits<CharT>>&>);
+
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ {
+ OS os{w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+ assert(os.get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ using W = std::basic_ostringstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+ static_assert(!std::convertible_to<std::basic_ostream<CharT, constexpr_char_traits<CharT>>&, OS>);
+ static_assert(std::constructible_from<OS, std::basic_ostream<CharT, constexpr_char_traits<CharT>>&>);
+
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ {
+ OS os{w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 1);
+#endif
+ assert(os.get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_wrapped() == w.rdbuf());
+ assert(os.rdbuf()->get_allocator() == test_allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(w.rdbuf()) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp
new file mode 100644
index 000000000000000..eb3ac284cc68e7c
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.allocator.pass.cpp
@@ -0,0 +1,132 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// basic_osyncstream(streambuf_type* os, const Allocator& allocator);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using OS = std::basic_osyncstream<CharT>;
+ using W = std::basic_stringbuf<CharT>;
+
+ const std::allocator<CharT> alloc;
+ {
+ OS os = {nullptr, alloc};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+ using W = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>>;
+
+ const std::allocator<CharT> alloc;
+ {
+ OS os = {nullptr, alloc};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ using W = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+ const test_allocator<CharT> alloc;
+ {
+ OS os = {nullptr, alloc};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp
new file mode 100644
index 000000000000000..c98a8894dd004a2
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/syncstream.osyncstream.cons/cons.pointer.pass.cpp
@@ -0,0 +1,140 @@
+//==----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// explicit basic_osyncstream(streambuf_type* os);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+#include <sstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using OS = std::basic_osyncstream<CharT>;
+ using W = std::basic_stringbuf<CharT>;
+
+ static_assert(!std::convertible_to<std::basic_syncbuf<CharT>*, OS>);
+ static_assert(std::constructible_from<OS, std::basic_syncbuf<CharT>*>);
+
+ {
+ OS os{nullptr};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>>;
+ using W = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>>;
+
+ static_assert(!std::convertible_to<std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>*, OS>);
+ static_assert(std::constructible_from<OS, std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>*>);
+
+ {
+ OS os{nullptr};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == std::allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using OS = std::basic_osyncstream<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ using W = std::basic_stringbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+ static_assert(
+ !std::convertible_to<std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>*, OS>);
+ static_assert(
+ std::constructible_from<OS, std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>*>);
+
+ {
+ OS os{nullptr};
+ assert(os.get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_wrapped() == nullptr);
+ assert(os.rdbuf()->get_allocator() == test_allocator<CharT>());
+ }
+ {
+ W w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ OS os{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(os.get_wrapped() == &w);
+ assert(os.rdbuf()->get_wrapped() == &w);
+ assert(os.rdbuf()->get_allocator() == test_allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp
new file mode 100644
index 000000000000000..4d7a54dbaa51806
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// Basic test whether the code works in a threaded environment.
+// Using timing the output order should be stable.
+// several_threads.pass.cpp tests with more threads.
+
+#include <syncstream>
+#include <sstream>
+#include <mutex>
+#include <thread>
+#include <cassert>
+#include <iostream>
+
+#include "test_macros.h"
+
+static std::basic_ostringstream<char> ss;
+static const char a = 'a';
+static const char b = 'b';
+static const char c = 'c';
+static const char d = 'd';
+
+void f1() {
+ std::basic_osyncstream<char> out(ss);
+ out << a;
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+ out << b;
+}
+
+void f2() {
+ std::basic_osyncstream<char> out(ss);
+ out << c;
+ out << d;
+}
+
+int main(int, char**) {
+ std::thread t1(f1);
+ std::thread t2(f2);
+ t1.join();
+ t2.join();
+
+ assert(ss.str() == "cdab");
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp
new file mode 100644
index 000000000000000..dbf2ded1b532e2b
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp
@@ -0,0 +1,144 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: no-threads
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_osyncstream;
+
+// The test writes all elements in test_strings in a random order in ss. Every
+// write is done by an osyncstream. This means the output is in random order,
+// but the words should be written without interleaving. To increment the
+// change of interleaving words are written one character at a time.
+
+#include <cassert>
+#include <chrono>
+#include <mutex>
+#include <sstream>
+#include <string>
+#include <syncstream>
+#include <thread>
+#include <unordered_set>
+#include <vector>
+
+#include "test_macros.h"
+
+static std::ostringstream ss;
+
+static std::unordered_multiset<std::string> test_strings = {
+ "C++ ",
+ "is ",
+ "a ",
+ "general-purpose ",
+ "programming ",
+ "language ",
+ "created ",
+ "by ",
+ "Bjarne ",
+ "Stroustrup ",
+ "as ",
+ "an ",
+ "extension ",
+ "of ",
+ "the ",
+ "C ",
+ "programming ",
+ "language, ",
+ "or ",
+ "C ",
+ "with ",
+ "Classes. ",
+ "The ",
+ "language ",
+ "has ",
+ "expanded ",
+ "significantly ",
+ "over ",
+ "time, ",
+ "and ",
+ "modern ",
+ "C++ ",
+ "has ",
+ "object-oriented, ",
+ "generic, ",
+ "and ",
+ "functional ",
+ "features ",
+ "in ",
+ "addition ",
+ "to ",
+ "facilities ",
+ "for ",
+ "low-level ",
+ "memory ",
+ "manipulation. ",
+ "It ",
+ "is ",
+ "almost ",
+ "always ",
+ "implemented ",
+ "as ",
+ "a ",
+ "compiled ",
+ "language, ",
+ "and ",
+ "many ",
+ "vendors ",
+ "provide ",
+ "C++ ",
+ "compilers, ",
+ "including ",
+ "the ",
+ "Free ",
+ "Software ",
+ "Foundation, ",
+ "LLVM, ",
+ "Microsoft, ",
+ "Intel, ",
+ "and ",
+ "IBM, ",
+ "so ",
+ "it ",
+ "is ",
+ "available ",
+ "on ",
+ "many ",
+ "platforms."};
+
+void f(std::string text) {
+ std::osyncstream out(ss);
+ for (char c : text)
+ out << c;
+}
+
+void test() {
+ ss = std::basic_ostringstream<char>();
+ std::vector<std::thread> threads;
+ for (std::string const& word : test_strings)
+ threads.push_back(std::thread(f, word));
+
+ for (auto& thread : threads)
+ thread.join();
+
+ std::string output = ss.str();
+ for (const std::string& word : test_strings)
+ assert(output.find(word) != std::string::npos);
+}
+
+int main(int, char**) {
+ // The more we test, the more likely we catch an error
+ for (size_t i = 0; i < 1024; ++i)
+ test();
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp
new file mode 100644
index 000000000000000..f222ad82f3650e4
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp
@@ -0,0 +1,92 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+//
+// template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+// class basic_osyncstream : public basic_ostream<charT, traits> {
+// public:
+// using char_type = charT;
+// using int_type = typename traits::int_type;
+// using pos_type = typename traits::pos_type;
+// using off_type = typename traits::off_type;
+// using traits_type = traits;
+//
+// using allocator_type = Allocator;
+// using streambuf_type = basic_streambuf<charT, traits>;
+// using syncbuf_type = basic_syncbuf<charT, traits, Allocator>;
+
+#include <syncstream>
+#include <concepts>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+static_assert(std::same_as<std::basic_osyncstream<char>,
+ std::basic_osyncstream<char, std::char_traits<char>, std::allocator<char>>>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>>,
+ std::basic_osyncstream<char, constexpr_char_traits<char>, std::allocator<char>>>);
+
+static_assert(
+ std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::char_type, char>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::int_type,
+ constexpr_char_traits<char>::int_type>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::pos_type,
+ constexpr_char_traits<char>::pos_type>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::off_type,
+ constexpr_char_traits<char>::off_type>);
+static_assert(std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::traits_type,
+ constexpr_char_traits<char>>);
+static_assert(
+ std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::allocator_type,
+ test_allocator<char>>);
+static_assert(
+ std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::streambuf_type,
+ std::basic_streambuf<char, constexpr_char_traits<char>>>);
+static_assert(
+ std::same_as<std::basic_osyncstream<char, constexpr_char_traits<char>, test_allocator<char>>::syncbuf_type,
+ std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>>);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+
+static_assert(std::same_as<std::basic_osyncstream<wchar_t>,
+ std::basic_osyncstream<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>>);
+static_assert(std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>>,
+ std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, std::allocator<wchar_t>>>);
+
+static_assert(
+ std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::char_type,
+ wchar_t>);
+static_assert(
+ std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::int_type,
+ constexpr_char_traits<wchar_t>::int_type>);
+static_assert(
+ std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::pos_type,
+ constexpr_char_traits<wchar_t>::pos_type>);
+static_assert(
+ std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::off_type,
+ constexpr_char_traits<wchar_t>::off_type>);
+static_assert(
+ std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::traits_type,
+ constexpr_char_traits<wchar_t>>);
+static_assert(std::same_as<
+ std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::allocator_type,
+ test_allocator<wchar_t>>);
+static_assert(std::same_as<
+ std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::streambuf_type,
+ std::basic_streambuf<wchar_t, constexpr_char_traits<wchar_t>>>);
+static_assert(
+ std::same_as<std::basic_osyncstream<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::syncbuf_type,
+ std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>>);
+
+#endif // TEST_HAS_NO_WIDE_CHARACTERS
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/helpers.h b/libcxx/test/std/input.output/syncstream/syncbuf/helpers.h
new file mode 100644
index 000000000000000..79fcaafa80956d2
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/helpers.h
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_INPUT_OUTPUT_SYNCSTREAM_SYNCBUF_SYNCSTREAM_SYNCBUF_MEMBERS_H
+#define TEST_STD_INPUT_OUTPUT_SYNCSTREAM_SYNCBUF_SYNCSTREAM_SYNCBUF_MEMBERS_H
+
+#include <syncstream>
+
+template <class T>
+class test_buf : public std::basic_streambuf<T> {
+public:
+ int id;
+
+ test_buf(int _id = 0) : id(_id) {}
+
+ T* _pptr() { return this->pptr(); }
+};
+
+template <class T, class Alloc = std::allocator<T>>
+class test_syncbuf : public std::basic_syncbuf<T, std::char_traits<T>, Alloc> {
+public:
+ test_syncbuf(test_buf<T>* buf, Alloc alloc) : std::basic_syncbuf<T, std::char_traits<T>, Alloc>(buf, alloc) {}
+
+ void _setp(T* begin, T* end) { return this->setp(begin, end); }
+};
+
+template <class T>
+struct test_allocator : std::allocator<T> {
+ int id;
+ test_allocator(int _id = 0) : id(_id) {}
+};
+
+#endif // TEST_STD_INPUT_OUTPUT_SYNCSTREAM_SYNCBUF_SYNCSTREAM_SYNCBUF_MEMBERS_H
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp
new file mode 100644
index 000000000000000..6812e0abb5924fc
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp
@@ -0,0 +1,151 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// Tests the inherited function using a custom allocator.
+//
+// int_type basic_streambuf<charT, traits>::sputc(char_type c);
+//
+// This test also validates the observable behaviour after move assignment and
+// construction. This test uses a small so the underlying string is in short
+// mode.
+
+#include <array>
+#include <syncstream>
+#include <cassert>
+#include <sstream>
+
+#include "test_macros.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ std::array< CharT, 17> input{
+ CharT('a'),
+ CharT('1'),
+ CharT('+'),
+ CharT('A'),
+ CharT('g'),
+ CharT('0'),
+ CharT('@'),
+ CharT('Z'),
+ CharT('q'),
+ CharT('8'),
+ CharT('#'),
+ CharT('D'),
+ CharT('t'),
+ CharT('9'),
+ CharT('$'),
+ CharT('A'),
+ CharT(' ')};
+ using SyncBuf = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT>>;
+
+ { // Normal
+ std::basic_string<CharT> expected;
+ std::basic_stringbuf<CharT> buf;
+ test_allocator_statistics stats;
+ test_allocator<CharT> allocator{&stats};
+
+ {
+ SyncBuf sync_buf{&buf, allocator};
+ for (int i = 0; i < 1024; ++i) {
+ CharT c = input[i % input.size()];
+ expected.push_back(c);
+ typename SyncBuf::int_type ret = sync_buf.sputc(c);
+ assert(ret == typename SyncBuf::int_type(c));
+ }
+ // The synchchronization happens upon destruction of sync_buf.
+ assert(buf.str().empty());
+ assert(stats.allocated_size >= 1024);
+ }
+ assert(buf.str() == expected);
+ assert(stats.allocated_size == 0);
+ }
+ { // Move construction
+ std::basic_stringbuf<CharT> buf;
+ test_allocator_statistics stats;
+ test_allocator<CharT> allocator{&stats};
+
+ {
+ SyncBuf sync_buf{&buf, allocator};
+ CharT c = CharT('4');
+ typename SyncBuf::int_type ret = sync_buf.sputc(c);
+ assert(ret == typename SyncBuf::int_type(c));
+
+ {
+ c = CharT('2');
+
+ SyncBuf new_sync_buf{std::move(sync_buf)};
+ ret = new_sync_buf.sputc(c);
+ assert(ret == typename SyncBuf::int_type(c));
+
+ // The synchchronization happens upon destruction of new_sync_buf.
+ assert(buf.str().empty());
+ assert(stats.allocated_size >= 2);
+ }
+ assert(buf.str().size() == 2);
+ assert(buf.str()[0] == CharT('4'));
+ assert(buf.str()[1] == CharT('2'));
+ assert(stats.allocated_size == 0);
+ }
+ assert(buf.str().size() == 2);
+ assert(buf.str()[0] == CharT('4'));
+ assert(buf.str()[1] == CharT('2'));
+ assert(stats.allocated_size == 0);
+ }
+ { // Move assignment non-propagating allocator
+ std::basic_stringbuf<CharT> buf;
+ test_allocator_statistics stats;
+ test_allocator<CharT> allocator{&stats};
+ static_assert(!std::allocator_traits<test_allocator<CharT>>::propagate_on_container_move_assignment::value);
+
+ {
+ SyncBuf sync_buf{&buf, allocator};
+ CharT c = CharT('4');
+ typename SyncBuf::int_type ret = sync_buf.sputc(c);
+ assert(ret == typename SyncBuf::int_type(c));
+
+ {
+ c = CharT('2');
+
+ SyncBuf new_sync_buf;
+ test_allocator<CharT> a = new_sync_buf.get_allocator();
+ new_sync_buf = std::move(sync_buf);
+ assert(new_sync_buf.get_allocator() == a);
+
+ ret = new_sync_buf.sputc(c);
+ assert(ret == typename SyncBuf::int_type(c));
+
+ // The synchchronization happens upon destruction of new_sync_buf.
+ assert(buf.str().empty());
+ }
+ assert(buf.str().size() == 2);
+ assert(buf.str()[0] == CharT('4'));
+ assert(buf.str()[1] == CharT('2'));
+ }
+ assert(buf.str().size() == 2);
+ assert(buf.str()[0] == CharT('4'));
+ assert(buf.str()[1] == CharT('2'));
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp
new file mode 100644
index 000000000000000..d706010843d62af
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp
@@ -0,0 +1,134 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// Tests the inherited function using a custom allocator.
+//
+// streamsize basic_streambuf<charT, traits>::sputc(const char_type* s, streamsize n);
+//
+// This test also validates the observable behaviour after move assignment and
+// construction. This test uses a large buffer so the underlying string is in
+// long mode.
+
+#include <array>
+#include <syncstream>
+#include <cassert>
+#include <sstream>
+
+#include "test_macros.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ std::array< CharT, 17> input{
+ CharT('a'),
+ CharT('1'),
+ CharT('+'),
+ CharT('A'),
+ CharT('g'),
+ CharT('0'),
+ CharT('@'),
+ CharT('Z'),
+ CharT('q'),
+ CharT('8'),
+ CharT('#'),
+ CharT('D'),
+ CharT('t'),
+ CharT('9'),
+ CharT('$'),
+ CharT('A'),
+ CharT(' ')};
+ std::basic_string<CharT> expected;
+ for (int i = 0; i < 1024; ++i)
+ expected.push_back(input[i % input.size()]);
+
+ using SyncBuf = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT>>;
+ { // Normal
+ std::basic_stringbuf<CharT> buf;
+ test_allocator_statistics stats;
+ test_allocator<CharT> allocator{&stats};
+
+ {
+ SyncBuf sync_buf{&buf, allocator};
+ std::streamsize ret = sync_buf.sputn(expected.data(), expected.size());
+ assert(ret == 1024);
+
+ // The synchchronization happens upon destruction of sync_buf.
+ assert(buf.str().empty());
+ assert(stats.allocated_size >= 1024);
+ }
+ assert(buf.str() == expected);
+ assert(stats.allocated_size == 0);
+ }
+ { // Move construction
+ std::basic_stringbuf<CharT> buf;
+ test_allocator_statistics stats;
+ test_allocator<CharT> allocator{&stats};
+
+ {
+ SyncBuf sync_buf{&buf, allocator};
+ std::streamsize ret = sync_buf.sputn(expected.data(), expected.size());
+ assert(ret == 1024);
+ {
+ SyncBuf new_sync_buf{std::move(sync_buf)};
+ ret = new_sync_buf.sputn(expected.data(), expected.size());
+ assert(ret == 1024);
+
+ // The synchchronization happens upon destruction of new_sync_buf.
+ assert(buf.str().empty());
+ assert(stats.allocated_size >= 2048);
+ }
+ assert(buf.str() == expected + expected);
+ assert(stats.allocated_size == 0);
+ }
+ assert(buf.str() == expected + expected);
+ assert(stats.allocated_size == 0);
+ }
+ { // Move assignment non-propagating allocator
+ std::basic_stringbuf<CharT> buf;
+ test_allocator_statistics stats;
+ test_allocator<CharT> allocator{&stats};
+ static_assert(!std::allocator_traits<test_allocator<CharT>>::propagate_on_container_move_assignment::value);
+
+ {
+ SyncBuf sync_buf{&buf, allocator};
+ std::streamsize ret = sync_buf.sputn(expected.data(), expected.size());
+ assert(ret == 1024);
+ {
+ SyncBuf new_sync_buf;
+ test_allocator<CharT> a = new_sync_buf.get_allocator();
+ new_sync_buf = std::move(sync_buf);
+ assert(new_sync_buf.get_allocator() == a);
+
+ ret = new_sync_buf.sputn(expected.data(), expected.size());
+ assert(ret == 1024);
+
+ // The synchchronization happens upon destruction of new_sync_buf.
+ assert(buf.str().empty());
+ }
+ assert(buf.str() == expected + expected);
+ }
+ assert(buf.str() == expected + expected);
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp
new file mode 100644
index 000000000000000..26e48ff9096aed9
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/sync.pass.cpp
@@ -0,0 +1,56 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// protected:
+// [syncstream.syncbuf.virtuals], overridden virtual functions
+// int sync() override;
+
+#include <syncstream>
+#include <sstream>
+#include <ostream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test_sync(bool emit_on_sync) {
+ std::basic_stringbuf<CharT> base;
+ std::basic_syncbuf<CharT> buff(&base);
+ std::basic_ostream<CharT> out(&buff);
+
+ buff.set_emit_on_sync(emit_on_sync);
+
+ out << 'a';
+ out.flush(); // This is an indirect call to sync.
+
+ if (emit_on_sync) {
+ assert(base.str().size() == 1);
+ assert(base.str()[0] == CharT('a'));
+ } else
+ assert(base.str().empty());
+}
+
+int main(int, char**) {
+ test_sync<char>(true);
+ test_sync<char>(false);
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test_sync<wchar_t>(true);
+ test_sync<wchar_t>(false);
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp
new file mode 100644
index 000000000000000..f2309075717eb12
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp
@@ -0,0 +1,399 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf& operator=(basic_syncbuf&& rhs);
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+#include <concepts>
+
+#include "test_macros.h"
+
+template <class T, class propagate>
+struct test_allocator : std::allocator<T> {
+ using propagate_on_container_move_assignment = propagate;
+
+ int id{-1};
+
+ test_allocator(int _id = -1) : id(_id) {}
+ test_allocator(test_allocator const& other) = default;
+ test_allocator(test_allocator&& other) = default;
+ test_allocator& operator=(const test_allocator& other) = default;
+
+ test_allocator& operator=(test_allocator&& other) {
+ if constexpr (propagate_on_container_move_assignment::value)
+ id = other.id;
+ else
+ id = -1;
+ return *this;
+ }
+};
+
+template <class T>
+class test_buf : public std::basic_streambuf<T> {
+public:
+ int id;
+
+ test_buf(int _id = 0) : id(_id) {}
+
+ T* _pptr() { return this->pptr(); }
+};
+
+template <class T, class Alloc = std::allocator<T>>
+class test_syncbuf : public std::basic_syncbuf<T, std::char_traits<T>, Alloc> {
+ using Base = std::basic_syncbuf<T, std::char_traits<T>, Alloc>;
+
+public:
+ test_syncbuf() = default;
+
+ test_syncbuf(test_buf<T>* buf, Alloc alloc) : Base(buf, alloc) {}
+
+ test_syncbuf(typename Base::streambuf_type* buf, Alloc alloc) : Base(buf, alloc) {}
+
+ void _setp(T* begin, T* end) { return this->setp(begin, end); }
+};
+
+// Helper wrapper to inspect the internal state of the basic_syncbuf
+//
+// This is used the valiate some standard requirements and libc++
+// implementation details.
+template <class CharT, class Traits, class Allocator>
+class syncbuf_inspector : public std::basic_syncbuf<CharT, Traits, Allocator> {
+public:
+ syncbuf_inspector() = default;
+ explicit syncbuf_inspector(std::basic_syncbuf<CharT, Traits, Allocator>&& base)
+ : std::basic_syncbuf<CharT, Traits, Allocator>(std::move(base)) {}
+
+ void operator=(std::basic_syncbuf<CharT, Traits, Allocator>&& base) { *this = std::move(base); }
+
+ using std::basic_syncbuf<CharT, Traits, Allocator>::pbase;
+ using std::basic_syncbuf<CharT, Traits, Allocator>::pptr;
+ using std::basic_syncbuf<CharT, Traits, Allocator>::epptr;
+};
+
+template <class CharT>
+static void test_assign() {
+ test_buf<CharT> base;
+
+ { // Test using the real class, propagating allocator.
+ using BuffT = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT, std::true_type>>;
+
+ BuffT buff1(&base, test_allocator<CharT, std::true_type>{42});
+ buff1.sputc(CharT('A'));
+
+ assert(buff1.get_wrapped() != nullptr);
+
+ BuffT buff2;
+ assert(buff2.get_allocator().id == -1);
+ buff2 = std::move(buff1);
+ assert(buff1.get_wrapped() == nullptr);
+ assert(buff2.get_wrapped() == &base);
+
+ assert(buff2.get_wrapped() == &base);
+ assert(buff2.get_allocator().id == 42);
+ }
+
+ { // Test using the real class, non-propagating allocator.
+ using BuffT = std::basic_syncbuf<CharT, std::char_traits<CharT>, test_allocator<CharT, std::false_type>>;
+
+ BuffT buff1(&base, test_allocator<CharT, std::false_type>{42});
+ buff1.sputc(CharT('A'));
+
+ assert(buff1.get_wrapped() != nullptr);
+
+ BuffT buff2;
+ assert(buff2.get_allocator().id == -1);
+ buff2 = std::move(buff1);
+ assert(buff1.get_wrapped() == nullptr);
+ assert(buff2.get_wrapped() == &base);
+
+ assert(buff2.get_wrapped() == &base);
+ assert(buff2.get_allocator().id == -1);
+ }
+
+ { // Move assignment propagating allocator
+ // Test using the inspection wrapper.
+ // Not all these requirements are explicitly in the Standard,
+ // however the asserts are based on secondary requirements. The
+ // LIBCPP_ASSERTs are implementation specific.
+
+ using BuffT = std::basic_syncbuf<CharT, std::char_traits<CharT>, std::allocator<CharT>>;
+
+ using Inspector = syncbuf_inspector<CharT, std::char_traits<CharT>, std::allocator<CharT>>;
+ Inspector inspector1{BuffT(&base)};
+ inspector1.sputc(CharT('A'));
+
+ assert(inspector1.get_wrapped() != nullptr);
+ assert(inspector1.pbase() != nullptr);
+ assert(inspector1.pptr() != nullptr);
+ assert(inspector1.epptr() != nullptr);
+ assert(inspector1.pbase() != inspector1.pptr());
+ assert(inspector1.pptr() - inspector1.pbase() == 1);
+ [[maybe_unused]] std::streamsize size = inspector1.epptr() - inspector1.pbase();
+
+ Inspector inspector2;
+ inspector2 = std::move(inspector1);
+
+ assert(inspector1.get_wrapped() == nullptr);
+ LIBCPP_ASSERT(inspector1.pbase() == nullptr);
+ LIBCPP_ASSERT(inspector1.pptr() == nullptr);
+ LIBCPP_ASSERT(inspector1.epptr() == nullptr);
+ assert(inspector1.pbase() == inspector1.pptr());
+
+ assert(inspector2.get_wrapped() == &base);
+ LIBCPP_ASSERT(inspector2.pbase() != nullptr);
+ LIBCPP_ASSERT(inspector2.pptr() != nullptr);
+ LIBCPP_ASSERT(inspector2.epptr() != nullptr);
+ assert(inspector2.pptr() - inspector2.pbase() == 1);
+ LIBCPP_ASSERT(inspector2.epptr() - inspector2.pbase() == size);
+ }
+}
+
+template <class CharT>
+static void test_basic() {
+ { // Test properties
+ std::basic_syncbuf<CharT> sync_buf1(nullptr);
+ std::basic_syncbuf<CharT> sync_buf2(nullptr);
+ [[maybe_unused]] std::same_as<std::basic_syncbuf<CharT>&> decltype(auto) ret =
+ sync_buf1.operator=(std::move(sync_buf2));
+ }
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 1);
+#endif
+
+ sync_buf2 = std::move(sync_buf1);
+ assert(sync_buf2.get_wrapped() == &sstr1);
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 0);
+#endif
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_short_write_after_assign() {
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf2 = std::move(sync_buf1);
+ sync_buf2.sputc(CharT('Z'));
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 2);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr1.str()[1] == CharT('Z'));
+ assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_long_write_after_assign() {
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf2 = std::move(sync_buf1);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 1 + expected.size());
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr1.str().substr(1) == expected);
+ assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_emit_on_assign() {
+ { // don't emit / don't emit
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(false);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(false);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf2 = std::move(sync_buf1);
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ { // don't emit / do emit
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(true);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(false);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf2 = std::move(sync_buf1);
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ { // do emit / don't emit
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(false);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(true);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf2 = std::move(sync_buf1);
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ { // do emit / do emit
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(true);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(true);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf2 = std::move(sync_buf1);
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+}
+
+template <class CharT>
+static void test() {
+ test_assign<CharT>();
+ test_basic<CharT>();
+ test_short_write_after_assign<CharT>();
+ test_long_write_after_assign<CharT>();
+ test_emit_on_assign<CharT>();
+}
+
+int main(int, char**) {
+ test<char>();
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp
new file mode 100644
index 000000000000000..ba007da5a054a7b
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp
@@ -0,0 +1,274 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// void swap(basic_syncbuf& other) noexcept;
+
+#include <syncstream>
+#include <sstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+#include <iostream>
+
+template <class CharT>
+static void test_basic() {
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 1);
+#endif
+
+ sync_buf1.swap(sync_buf2);
+ assert(sync_buf1.get_wrapped() == &sstr2);
+ assert(sync_buf2.get_wrapped() == &sstr1);
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr1) == 1);
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&sstr2) == 1);
+#endif
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_short_write_after_swap() {
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf1.swap(sync_buf2);
+ expected.push_back(sync_buf1.sputc(CharT('B')));
+ sync_buf2.sputc(CharT('Z'));
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+ }
+
+ assert(sstr1.str().size() == 2);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr1.str()[1] == CharT('Z'));
+ assert(sstr2.str() == expected);
+}
+
+template <class CharT>
+static void test_long_write_after_swap() {
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf1.swap(sync_buf2);
+ sync_buf1.sputn(expected.data(), expected.size());
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+ }
+
+ assert(sstr1.str().size() == 1 + expected.size());
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr1.str().substr(1) == expected);
+ assert(sstr2.str() == expected + expected);
+}
+
+template <class CharT>
+static void test_emit_on_sync() {
+ { // false false
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(false);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(false);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf1.swap(sync_buf2);
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+
+ sync_buf1.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ { // false true
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(true);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(false);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf1.swap(sync_buf2);
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+
+ sync_buf1.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str().empty());
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ { // true false
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(false);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(true);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf1.swap(sync_buf2);
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+
+ sync_buf1.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ { // true true
+
+ std::basic_stringbuf<CharT> sstr1;
+ std::basic_stringbuf<CharT> sstr2;
+ std::basic_string<CharT> expected(42, CharT('*')); // a long string
+
+ {
+ std::basic_syncbuf<CharT> sync_buf1(&sstr1);
+ sync_buf1.set_emit_on_sync(true);
+ sync_buf1.sputc(CharT('A')); // a short string
+
+ std::basic_syncbuf<CharT> sync_buf2(&sstr2);
+ sync_buf2.set_emit_on_sync(true);
+ sync_buf2.sputn(expected.data(), expected.size());
+
+ sync_buf1.swap(sync_buf2);
+
+ assert(sstr1.str().empty());
+ assert(sstr2.str().empty());
+
+ sync_buf1.pubsync();
+ assert(sstr1.str().empty());
+ assert(sstr2.str() == expected);
+
+ sync_buf2.pubsync();
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+
+ assert(sstr1.str().size() == 1);
+ assert(sstr1.str()[0] == CharT('A'));
+ assert(sstr2.str() == expected);
+ }
+}
+
+template <class CharT>
+static void test() {
+ test_basic<CharT>();
+ test_emit_on_sync<CharT>();
+ test_short_write_after_swap<CharT>();
+ test_long_write_after_swap<CharT>();
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp
new file mode 100644
index 000000000000000..aa0eb2d41e0f014
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf();
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using Buf = std::basic_syncbuf<CharT>;
+ static_assert(std::default_initializable<Buf>);
+ Buf buf;
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == std::allocator<CharT>());
+ }
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+ static_assert(std::default_initializable<Buf>);
+ Buf buf;
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == std::allocator<CharT>());
+ }
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ static_assert(std::default_initializable<Buf>);
+ Buf buf;
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == test_allocator<CharT>());
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp
new file mode 100644
index 000000000000000..d12b3d0f9ff9f3d
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.move.pass.cpp
@@ -0,0 +1,136 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf(basic_syncbuf&& other);
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using Buf = std::basic_syncbuf<CharT>;
+ const std::allocator<CharT> alloc;
+ {
+ Buf b1{nullptr, alloc};
+ Buf b2{std::move(b1)};
+
+ assert(b2.get_wrapped() == nullptr);
+ assert(b2.get_allocator() == alloc);
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf b1{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ Buf b2{std::move(b1)};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(b2.get_wrapped() == &w);
+ assert(b2.get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+ const std::allocator<CharT> alloc;
+ {
+ Buf b1{nullptr, alloc};
+ Buf b2{std::move(b1)};
+
+ assert(b2.get_wrapped() == nullptr);
+ assert(b2.get_allocator() == alloc);
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf b1{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ Buf b2{std::move(b1)};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(b2.get_wrapped() == &w);
+ assert(b2.get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ test_allocator<CharT> alloc{42};
+ {
+ Buf b1{nullptr, alloc};
+ Buf b2{std::move(b1)};
+
+ assert(b2.get_wrapped() == nullptr);
+ assert(b2.get_allocator() == alloc);
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf b1{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ Buf b2{std::move(b1)};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(b2.get_wrapped() == &w);
+ assert(b2.get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp
new file mode 100644
index 000000000000000..faecfa9ac58faf3
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp
@@ -0,0 +1,119 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// basic_syncbuf(streambuf_type* obuf, const Allocator&);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using Buf = std::basic_syncbuf<CharT>;
+ const std::allocator<CharT> alloc;
+ {
+ Buf buf = {nullptr, alloc};
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == alloc);
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf buf = {&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(buf.get_wrapped() == &w);
+ assert(buf.get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+ const std::allocator<CharT> alloc;
+ {
+ Buf buf = {nullptr, alloc};
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == alloc);
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf buf{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(buf.get_wrapped() == &w);
+ assert(buf.get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+ test_allocator<CharT> alloc{42};
+ {
+ Buf buf = {nullptr, alloc};
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == alloc);
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf buf{&w, alloc};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(buf.get_wrapped() == &w);
+ assert(buf.get_allocator() == alloc);
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp
new file mode 100644
index 000000000000000..ed5eff75737e79c
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.pass.cpp
@@ -0,0 +1,128 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// explicit basic_syncbuf(streambuf_type* obuf);
+
+#include <cassert>
+#include <concepts>
+#include <syncstream>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+template <class CharT>
+void test() {
+ {
+ using Buf = std::basic_syncbuf<CharT>;
+
+ static_assert(!std::convertible_to<std::basic_streambuf<CharT>*, Buf>);
+ static_assert(std::constructible_from<Buf, std::basic_streambuf<CharT>*>);
+
+ {
+ Buf buf{nullptr};
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == std::allocator<CharT>());
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf buf{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(buf.get_wrapped() == &w);
+ assert(buf.get_allocator() == std::allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>>;
+
+ static_assert(!std::convertible_to<std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*, Buf>);
+ static_assert(std::constructible_from<Buf, std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*>);
+
+ {
+ Buf buf{nullptr};
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == std::allocator<CharT>());
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf buf{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(buf.get_wrapped() == &w);
+ assert(buf.get_allocator() == std::allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+
+ {
+ using Buf = std::basic_syncbuf<CharT, constexpr_char_traits<CharT>, test_allocator<CharT>>;
+
+ static_assert(!std::convertible_to<std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*, Buf>);
+ static_assert(std::constructible_from<Buf, std::basic_streambuf<CharT, constexpr_char_traits<CharT>>*>);
+
+ {
+ Buf buf{nullptr};
+ assert(buf.get_wrapped() == nullptr);
+ assert(buf.get_allocator() == test_allocator<CharT>());
+ }
+ {
+ Buf w;
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ {
+ Buf buf{&w};
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1);
+#endif
+ assert(buf.get_wrapped() == &w);
+ assert(buf.get_allocator() == test_allocator<CharT>());
+ }
+#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS)
+ assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 0);
+#endif
+ }
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp
new file mode 100644
index 000000000000000..57ab447d96ccf25
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/dtor.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// ~basic_syncbuf();
+
+#include <syncstream>
+#include <cassert>
+
+#include "../helpers.h"
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+ // We do this because we want to be able to use CharT
+ CharT arr[3] = {'a', 'b', 'c'};
+ CharT* ptr = arr;
+
+ test_buf<CharT> base;
+ const std::allocator<CharT> alloc;
+ {
+ test_syncbuf<CharT> buf(&base, alloc);
+
+ buf._setp(ptr, ptr + 3);
+ assert(base._pptr() == nullptr);
+ // The destructor calls buf.emit();
+ }
+ CharT* pptr = base._pptr();
+ while (pptr) {
+ assert(*pptr++ == *ptr++);
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp
new file mode 100644
index 000000000000000..8905b0494d8f500
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// bool emit();
+
+#include <syncstream>
+#include <cassert>
+
+#include "../helpers.h"
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+ // We do this because we want to be able to use CharT
+ CharT arr[3] = {'a', 'b', 'c'};
+ CharT* ptr = arr;
+
+ test_buf<CharT> base;
+ const std::allocator<CharT> alloc;
+ test_syncbuf<CharT> buff(&base, alloc);
+
+ buff._setp(ptr, ptr + 3);
+ assert(base._pptr() == nullptr);
+ buff.emit();
+ CharT* pptr = base._pptr();
+ while (pptr) {
+ assert(*pptr++ == *ptr++);
+ }
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp
new file mode 100644
index 000000000000000..d722acea0b3796f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// allocator_type get_allocator() const noexcept;
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "../helpers.h"
+
+template <class T>
+void test_get_allocator() {
+ test_buf<T> base;
+ test_allocator<T> alloc(42);
+ const test_syncbuf<T, test_allocator<T>> buff(&base, alloc);
+ assert(buff.get_allocator().id == 42);
+ ASSERT_NOEXCEPT(buff.get_allocator());
+}
+
+int main(int, char**) {
+ test_get_allocator<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test_get_allocator<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp
new file mode 100644
index 000000000000000..b8443ac5e523d8f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// streambuf_type* get_wrapped() const noexcept;
+
+#include <syncstream>
+#include <concepts>
+#include <cassert>
+
+#include "../helpers.h"
+#include "test_macros.h"
+
+template <class T>
+void test() {
+ test_buf<T> base(42);
+ const std::allocator<T> alloc;
+ const test_syncbuf<T> buff(&base, alloc);
+ std::same_as<std::basic_streambuf<T>*> auto wrapped = buff.get_wrapped();
+ assert(static_cast<test_buf<T>*>(wrapped)->id == 42);
+ ASSERT_NOEXCEPT(buff.get_wrapped());
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp
new file mode 100644
index 000000000000000..7630e7e87c1e128
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template <class charT, class traits, class Allocator>
+// class basic_syncbuf;
+
+// void set_emit_on_sync(bool) noexcept;
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+#include "../helpers.h"
+
+template <class T>
+void test_set_emit_on_sync() {
+ // set_emit_on_sync tested in sync, which is called by pubsync. The assign
+ // and swap test use this.
+ test_syncbuf<T, std::allocator<T>> buff(nullptr, std::allocator<T>());
+ ASSERT_NOEXCEPT(buff.set_emit_on_sync(false));
+ buff.set_emit_on_sync(false); // Validates the function can be called.
+}
+
+int main(int, char**) {
+ test_set_emit_on_sync<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test_set_emit_on_sync<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp
new file mode 100644
index 000000000000000..d1ec4711f3f499a
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template<class charT, class traits, class Allocator>
+// void swap(basic_syncbuf<charT, traits, Allocator>&,
+// basic_syncbuf<charT, traits, Allocator>&);
+
+#include <syncstream>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+void test() {
+ std::basic_syncbuf<CharT> base1;
+ std::basic_syncbuf<CharT> base2;
+ std::basic_syncbuf<CharT> buff1(&base1);
+ std::basic_syncbuf<CharT> buff2(&base2);
+ std::swap(buff1, buff2);
+
+ assert(buff1.get_wrapped() == &base2);
+ assert(buff2.get_wrapped() == &base1);
+}
+
+int main(int, char**) {
+ test<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+ test<wchar_t>();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp
new file mode 100644
index 000000000000000..5fe11775505513f
--- /dev/null
+++ b/libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: no-localization
+// UNSUPPORTED: libcpp-has-no-experimental-syncstream
+
+// <syncstream>
+
+// template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
+// class basic_syncbuf {
+//
+// public:
+// using char_type = charT;
+// using int_type = typename traits::int_type;
+// using pos_type = typename traits::pos_type;
+// using off_type = typename traits::off_type;
+// using traits_type = traits;
+// using allocator_type = Allocator;
+//
+// using streambuf_type = basic_streambuf<charT, traits>;
+//
+// ...
+
+#include <syncstream>
+#include <concepts>
+
+#include "test_macros.h"
+#include "constexpr_char_traits.h"
+#include "test_allocator.h"
+
+static_assert(
+ std::same_as<std::basic_syncbuf<char>, std::basic_syncbuf<char, std::char_traits<char>, std::allocator<char>>>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>>,
+ std::basic_syncbuf<char, constexpr_char_traits<char>, std::allocator<char>>>);
+
+static_assert(
+ std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::char_type, char>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::int_type,
+ constexpr_char_traits<char>::int_type>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::pos_type,
+ constexpr_char_traits<char>::pos_type>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::off_type,
+ constexpr_char_traits<char>::off_type>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::traits_type,
+ constexpr_char_traits<char>>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::allocator_type,
+ test_allocator<char>>);
+static_assert(std::same_as<std::basic_syncbuf<char, constexpr_char_traits<char>, test_allocator<char>>::streambuf_type,
+ std::basic_streambuf<char, constexpr_char_traits<char>>>);
+
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+static_assert(std::same_as<std::basic_syncbuf<wchar_t>,
+ std::basic_syncbuf<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t>>>);
+static_assert(std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>>,
+ std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, std::allocator<wchar_t>>>);
+
+static_assert(
+ std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::char_type,
+ wchar_t>);
+static_assert(
+ std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::int_type,
+ constexpr_char_traits<wchar_t>::int_type>);
+static_assert(
+ std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::pos_type,
+ constexpr_char_traits<wchar_t>::pos_type>);
+static_assert(
+ std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::off_type,
+ constexpr_char_traits<wchar_t>::off_type>);
+static_assert(
+ std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::traits_type,
+ constexpr_char_traits<wchar_t>>);
+static_assert(
+ std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::allocator_type,
+ test_allocator<wchar_t>>);
+static_assert(
+ std::same_as<std::basic_syncbuf<wchar_t, constexpr_char_traits<wchar_t>, test_allocator<wchar_t>>::streambuf_type,
+ std::basic_streambuf<wchar_t, constexpr_char_traits<wchar_t>>>);
+#endif // TEST_HAS_NO_WIDE_CHARACTERS
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp
new file mode 100644
index 000000000000000..88769d2770b1bd5
--- /dev/null
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// WARNING: This test was generated by generate_feature_test_macro_components.py
+// and should not be edited manually.
+//
+// clang-format off
+
+// UNSUPPORTED: no-localization
+
+// <syncstream>
+
+// Test the feature test macros defined by <syncstream>
+
+/* Constant Value
+ __cpp_lib_syncbuf 201803L [C++20]
+*/
+
+#include <syncstream>
+#include "test_macros.h"
+
+#if TEST_STD_VER < 14
+
+# ifdef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should not be defined before c++20"
+# endif
+
+#elif TEST_STD_VER == 14
+
+# ifdef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should not be defined before c++20"
+# endif
+
+#elif TEST_STD_VER == 17
+
+# ifdef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should not be defined before c++20"
+# endif
+
+#elif TEST_STD_VER == 20
+
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+# ifndef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should be defined in c++20"
+# endif
+# if __cpp_lib_syncbuf != 201803L
+# error "__cpp_lib_syncbuf should have the value 201803L in c++20"
+# endif
+# else
+# ifdef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
+# endif
+# endif
+
+#elif TEST_STD_VER == 23
+
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+# ifndef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should be defined in c++23"
+# endif
+# if __cpp_lib_syncbuf != 201803L
+# error "__cpp_lib_syncbuf should have the value 201803L in c++23"
+# endif
+# else
+# ifdef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
+# endif
+# endif
+
+#elif TEST_STD_VER > 23
+
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
+# ifndef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should be defined in c++26"
+# endif
+# if __cpp_lib_syncbuf != 201803L
+# error "__cpp_lib_syncbuf should have the value 201803L in c++26"
+# endif
+# else
+# ifdef __cpp_lib_syncbuf
+# error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
+# endif
+# endif
+
+#endif // TEST_STD_VER > 23
+
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 3cc7ed5f6d22271..0c1b8fa997ffd85 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -3822,16 +3822,16 @@
# error "__cpp_lib_submdspan should not be defined before c++26"
# endif
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
# ifndef __cpp_lib_syncbuf
# error "__cpp_lib_syncbuf should be defined in c++20"
# endif
# if __cpp_lib_syncbuf != 201803L
# error "__cpp_lib_syncbuf should have the value 201803L in c++20"
# endif
-# else // _LIBCPP_VERSION
+# else
# ifdef __cpp_lib_syncbuf
-# error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!"
+# error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
# endif
# endif
@@ -5294,16 +5294,16 @@
# error "__cpp_lib_submdspan should not be defined before c++26"
# endif
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
# ifndef __cpp_lib_syncbuf
# error "__cpp_lib_syncbuf should be defined in c++23"
# endif
# if __cpp_lib_syncbuf != 201803L
# error "__cpp_lib_syncbuf should have the value 201803L in c++23"
# endif
-# else // _LIBCPP_VERSION
+# else
# ifdef __cpp_lib_syncbuf
-# error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!"
+# error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
# endif
# endif
@@ -6868,16 +6868,16 @@
# endif
# endif
-# if !defined(_LIBCPP_VERSION)
+# if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
# ifndef __cpp_lib_syncbuf
# error "__cpp_lib_syncbuf should be defined in c++26"
# endif
# if __cpp_lib_syncbuf != 201803L
# error "__cpp_lib_syncbuf should have the value 201803L in c++26"
# endif
-# else // _LIBCPP_VERSION
+# else
# ifdef __cpp_lib_syncbuf
-# error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!"
+# error "__cpp_lib_syncbuf should not be defined when the requirement '!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)' is not met!"
# endif
# endif
diff --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h
index 3bde73183ab6e39..8ae04413f8a93c8 100644
--- a/libcxx/test/support/test_allocator.h
+++ b/libcxx/test/support/test_allocator.h
@@ -29,8 +29,9 @@ TEST_CONSTEXPR_CXX20 inline typename std::allocator_traits<Alloc>::size_type all
struct test_allocator_statistics {
int time_to_throw = 0;
int throw_after = INT_MAX;
- int count = 0;
- int alloc_count = 0;
+ int count = 0; // the number of active instances
+ int alloc_count = 0; // the number of allocations not deallocating
+ int allocated_size = 0; // the size of allocated elements
int construct_count = 0; // the number of times that ::construct was called
int destroy_count = 0; // the number of times that ::destroy was called
int copied = 0;
@@ -42,6 +43,7 @@ struct test_allocator_statistics {
count = 0;
time_to_throw = 0;
alloc_count = 0;
+ allocated_size = 0;
construct_count = 0;
destroy_count = 0;
throw_after = INT_MAX;
@@ -155,14 +157,17 @@ class test_allocator {
TEST_THROW(std::bad_alloc());
++stats_->time_to_throw;
++stats_->alloc_count;
+ stats_->allocated_size += n;
}
return std::allocator<value_type>().allocate(n);
}
TEST_CONSTEXPR_CXX14 void deallocate(pointer p, size_type s) {
assert(data_ != test_alloc_base::destructed_value);
- if (stats_ != nullptr)
+ if (stats_ != nullptr) {
--stats_->alloc_count;
+ stats_->allocated_size -= s;
+ }
std::allocator<value_type>().deallocate(p, s);
}
@@ -275,6 +280,7 @@ class other_allocator {
}
TEST_CONSTEXPR_CXX14 friend bool operator!=(const other_allocator& x, const other_allocator& y) { return !(x == y); }
+ TEST_CONSTEXPR int get_data() const { return data_; }
typedef std::true_type propagate_on_container_copy_assignment;
typedef std::true_type propagate_on_container_move_assignment;
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 774fb6155398f90..98cf995ec223651 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -1023,7 +1023,8 @@ def add_version_header(tc):
"name": "__cpp_lib_syncbuf",
"values": {"c++20": 201803},
"headers": ["syncstream"],
- "unimplemented": True,
+ "test_suite_guard": "!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)",
+ "libcxx_guard": "!defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)",
},
{
"name": "__cpp_lib_text_encoding",
@@ -1180,6 +1181,7 @@ def add_version_header(tc):
"semaphore": ["UNSUPPORTED: no-threads"],
"shared_mutex": ["UNSUPPORTED: no-threads"],
"sstream": ["UNSUPPORTED: no-localization"],
+ "syncstream": ["UNSUPPORTED: no-localization"],
"stdatomic.h": ["UNSUPPORTED: no-threads"],
"stop_token": ["UNSUPPORTED: no-threads"],
"thread": ["UNSUPPORTED: no-threads"],
diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py
index db6f3246f6ba712..5fdb027e22b114f 100644
--- a/libcxx/utils/libcxx/header_information.py
+++ b/libcxx/utils/libcxx/header_information.py
@@ -30,6 +30,7 @@
"sstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
"streambuf": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
"strstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
+ "syncstream": "!defined(_LIBCPP_HAS_NO_LOCALIZATION)",
# headers with #error directives
"barrier": "!defined(_LIBCPP_HAS_NO_THREADS)",
@@ -92,6 +93,7 @@
"stop_token": "// UNSUPPORTED: no-threads, c++03, c++11, c++14, c++17",
"streambuf": "// UNSUPPORTED: no-localization",
"strstream": "// UNSUPPORTED: no-localization",
+ "syncstream": "// UNSUPPORTED: no-localization",
"thread": "// UNSUPPORTED: no-threads, c++03",
"wchar.h": "// UNSUPPORTED: no-wide-characters",
"wctype.h": "// UNSUPPORTED: no-wide-characters",
@@ -127,7 +129,7 @@
"stack": ["compare", "initializer_list"],
"string_view": ["compare"],
"string": ["compare", "initializer_list"],
- # TODO "syncstream": ["ostream"],
+ "syncstream": ["ostream"],
"system_error": ["compare"],
"tgmath.h": ["cmath", "complex"],
"thread": ["compare"],
@@ -156,7 +158,6 @@
"spanstream",
"stacktrace",
"stdfloat",
- "syncstream",
"text_encoding",
]
diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py
index 2ead641354e585d..cf190cf40d1cbb0 100644
--- a/libcxx/utils/libcxx/test/params.py
+++ b/libcxx/utils/libcxx/test/params.py
@@ -267,6 +267,7 @@ def getStdFlag(cfg, std):
AddFeature("libcpp-has-no-incomplete-pstl"),
AddFeature("libcpp-has-no-experimental-stop_token"),
AddFeature("libcpp-has-no-incomplete-tzdb"),
+ AddFeature("libcpp-has-no-experimental-syncstream"),
],
),
Parameter(
More information about the libcxx-commits
mailing list