diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -286,7 +286,7 @@ --------------------------------------------------- ----------------- ``__cpp_lib_string_view`` ``201803L`` --------------------------------------------------- ----------------- - ``__cpp_lib_syncbuf`` *unimplemented* + ``__cpp_lib_syncbuf`` ``201803L`` --------------------------------------------------- ----------------- ``__cpp_lib_three_way_comparison`` *unimplemented* --------------------------------------------------- ----------------- diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -114,7 +114,7 @@ "`3096 `__","``path::lexically_relative``\ is confused by trailing slashes","San Diego","|Complete|","" "`3116 `__","``OUTERMOST_ALLOC_TRAITS``\ needs ``remove_reference_t``\ ","San Diego","","" "`3122 `__","``__cpp_lib_chrono_udls``\ was accidentally dropped","San Diego","|Complete|","" -"`3127 `__","``basic_osyncstream::rdbuf``\ needs a ``const_cast``\ ","San Diego","","" +"`3127 `__","``basic_osyncstream::rdbuf``\ needs a ``const_cast``\ ","San Diego","|Complete|","18.0" "`3128 `__","``strstream::rdbuf``\ needs a ``const_cast``\ ","San Diego","|Nothing To Do|","" "`3129 `__","``regex_token_iterator``\ constructor uses wrong pointer arithmetic","San Diego","","" "`3130 `__","|sect|\ [input.output] needs many ``addressof``\ ","San Diego","","" @@ -254,7 +254,8 @@ "`3330 `__","Include ````\ from most library headers","Prague","|Complete|","13.0","|spaceship|" "`3331 `__","Define ``totally_ordered/_with``\ in terms of ``partially-ordered-with``\ ","Prague","|Complete|","13.0" "`3332 `__","Issue in |sect|\ [time.format]","Prague","|Complete|","16.0","|chrono| |format|" -"`3334 `__","``basic_osyncstream``\ move assignment and destruction calls ``basic_syncbuf::emit()``\ twice","Prague","","" +"`3334 `__","``basic_osyncstream``\ move +assignment and destruction calls ``basic_syncbuf::emit()``\ twice","Prague","|Complete|","18.0" "`3335 `__","Resolve C++20 NB comments US 273 and GB 274","Prague","|Complete|","15.0","|ranges|" "`3338 `__","Rename ``default_constructible``\ to ``default_initializable``\ ","Prague","|Complete|","13.0" "`3340 `__","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 --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -3,7 +3,7 @@ "`P0674R1 `__","LWG","Extending make_shared to Support Arrays","Toronto","|Complete|","15.0" "","","","","","","" "`P0020R6 `__","LWG","Floating Point Atomic","Albuquerque","","" -"`P0053R7 `__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","","" +"`P0053R7 `__","LWG","C++ Synchronized Buffered Ostream","Albuquerque","|Complete|","18.0" "`P0202R3 `__","LWG","Add constexpr modifiers to functions in and Headers","Albuquerque","|Complete|","12.0" "`P0415R1 `__","LWG","Constexpr for ``std::complex``\ ","Albuquerque","|Complete|","16.0" "`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 --- a/libcxx/docs/Status/Cxx23Issues.csv +++ b/libcxx/docs/Status/Cxx23Issues.csv @@ -122,7 +122,7 @@ `3566 `__,"Constraint recursion for ``operator<=>(optional, U)``","October 2021","|Complete|","17.0","|spaceship|" `3567 `__,"Formatting move-only iterators take two","October 2021","|Complete|","16.0","|format| |ranges|" `3568 `__,"``basic_istream_view`` needs to initialize ``value_``","October 2021","|Complete|","16.0","|ranges|" -`3570 `__,"``basic_osyncstream::emit`` should be an unformatted output function","October 2021","","" +`3570 `__,"``basic_osyncstream::emit`` should be an unformatted output function","October 2021","|Complete|","18.0" `3571 `__,"``flush_emit`` should set ``badbit`` if the ``emit`` call fails","October 2021","","" `3572 `__,"``copyable-box`` should be fully ``constexpr``","October 2021","|Complete|","14.0","|ranges|" `3573 `__,"Missing Throws element for ``basic_string_view(It begin, End end)``","October 2021","|Complete|","14.0" @@ -275,7 +275,7 @@ "`3857 `__","``basic_string_view`` should allow explicit conversion when only traits vary","February 2023","|Complete|","17.0","" "`3860 `__","``range_common_reference_t`` is missing","February 2023","|Complete|","17.0","|ranges|" "`3866 `__","Bad Mandates for ``expected::transform_error`` overloads","February 2023","|Complete|","17.0","" -"`3867 `__","Should ``std::basic_osyncstream``'s move assignment operator be ``noexcept``?","February 2023","","","" +"`3867 `__","Should ``std::basic_osyncstream``'s move assignment operator be ``noexcept``?","February 2023","|Complete|","18.0","" "`3441 `__","Misleading note about calls to customization points","February 2023","","","" "`3622 `__","Misspecified transitivity of equivalence in ยง[unord.req.general]","February 2023","","","" "`3631 `__","``basic_format_arg(T&&)`` should use ``remove_cvref_t`` throughout","February 2023","|Complete|","17.0","" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -984,6 +984,7 @@ string.h string_view strstream + syncstream system_error tgmath.h thread diff --git a/libcxx/include/__std_clang_module b/libcxx/include/__std_clang_module --- a/libcxx/include/__std_clang_module +++ b/libcxx/include/__std_clang_module @@ -201,6 +201,7 @@ #if !defined(_LIBCPP_HAS_NO_LOCALIZATION) # include #endif +#include #include #include #if !defined(_LIBCPP_HAS_NO_THREADS) diff --git a/libcxx/include/iosfwd b/libcxx/include/iosfwd --- a/libcxx/include/iosfwd +++ b/libcxx/include/iosfwd @@ -90,6 +90,18 @@ using u16streampos = fpos::state_type>; using u32streampos = fpos::state_type>; +template , class Allocator = allocator> + class basic_syncbuf; // C++20 + +using syncbuf = basic_syncbuf; // C++20 +using wsyncbuf = basic_syncbuf; // C++20 + +template , class Allocator = allocator> + class basic_osyncstream; // C++20 + +using osyncstream = basic_osyncstream; // C++20 +using wosyncstream = basic_osyncstream; // C++20 + } // std */ @@ -130,6 +142,22 @@ typedef fpos u16streampos; typedef fpos u32streampos; +#if _LIBCPP_STD_VER >= 20 + +template , class _Allocator = allocator<_CharT>> +class basic_syncbuf; + +using syncbuf = basic_syncbuf; +using wsyncbuf = basic_syncbuf; + +template , class _Allocator = allocator<_CharT>> +class basic_osyncstream; + +using osyncstream = basic_osyncstream; +using wosyncstream = basic_osyncstream; + +#endif // _LIBCPP_STD_VER >=20 + // Include other forward declarations here template > class _LIBCPP_TEMPLATE_VIS vector; diff --git a/libcxx/include/map b/libcxx/include/map --- a/libcxx/include/map +++ b/libcxx/include/map @@ -1699,6 +1699,8 @@ _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/syncstream b/libcxx/include/syncstream new file mode 100644 --- /dev/null +++ b/libcxx/include/syncstream @@ -0,0 +1,530 @@ +// -*- C++ -*- +//===--------------------------- sstream ----------------------------------===// +// +// 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 // see [ostream.syn] + +namespace std { + template + class basic_syncbuf; + + // [syncstream.syncbuf.special], specialized algorithms + template + void swap(basic_syncbuf&, + basic_syncbuf&); + + using syncbuf = basic_syncbuf; + using wsyncbuf = basic_syncbuf; + + template + class basic_osyncstream; + + using osyncstream = basic_osyncstream; + using wosyncstream = basic_osyncstream; + + template + class basic_syncbuf : public basic_streambuf { + 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; + + // [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 + void swap(basic_syncbuf&, + basic_syncbuf&); + + template + class basic_osyncstream : public basic_ostream { + 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; + using syncbuf_type = basic_syncbuf; + + // [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& os, const Allocator& allocator) + : basic_osyncstream(os.rdbuf(), allocator) {} + explicit basic_osyncstream(basic_ostream& 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(addressof(sb)); } + + private: + syncbuf_type sb; // exposition only + }; +} + +*/ + +#include <__config> +#include <__utility/move.h> +#include // required for declaration of default arguments +#include + +#ifndef _LIBCPP_HAS_NO_THREADS +# include +# include +# include +#endif + +// standard-mandated includes + +// [syncstream.syn] +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 20 + +// [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(__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 __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(__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 __lut_; + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI map::iterator __get_it(void* __ptr) noexcept { + _LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to"); + + auto __it = __lut_.find(reinterpret_cast(__ptr)); + _LIBCPP_ASSERT_INTERNAL(__it != __lut_.end(), "using a wrapped streambuf that has not been registered"); + _LIBCPP_ASSERT_INTERNAL(__it >= 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 constuctor is passed to the +// basic_string. The class does not keep a copy of this allocator. +template +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_NO_EXCEPTIONS + try { +# endif // _LIBCPP_NO_EXCEPTIONS + emit(); +# ifndef _LIBCPP_NO_EXCEPTIONS + } catch (...) { + } +# endif // _LIBCPP_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"); + +# if 1 + basic_syncbuf __tmp(std::move(__other)); + __other = std::move(*this); + *this = std::move(__tmp); +# else + + // + // Maybe needed, see comment for __str_ + // + + ptrdiff_t __this_size = this->pptr() - this->pbase(); + ptrdiff_t __other_size = __other.pptr() - __other.pbase(); + + std::swap(__wrapped_, __other.__wrapped_); + std::swap(__str_, __other.__str_); + std::swap(__emit_on_sync_, __other.__emit_on_sync_); + /* + std::cerr << "T: " << __str_ << '\n'; + std::cerr << "O: " << __other.__str_ << '\n'; + */ + this->setp(__str_.data(), __str_.data() + __str_.size()); + this->pbump(__other_size); + + __other.setp(__other.__str_.data(), __other.__str_.data() + __other.__str_.size()); + __other.pbump(__this_size); +# endif + } + + // [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_NO_EXCEPTIONS + try { +# endif // _LIBCPP_NO_EXCEPTIONS + 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(__str_.data()); + this->setp(__p, __p + __str_.size()); + this->pbump(__size); + +# ifndef _LIBCPP_NO_EXCEPTIONS + } catch (...) { + return traits_type::eof(); + } +# endif // _LIBCPP_NO_EXCEPTIONS + } + + return this->sputc(traits_type::to_char_type(__c)); + } + +private: + streambuf_type* __wrapped_; + + // + // + // Review note. + // This string could be removed and using raw memory instead. + // + // + + 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 __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(__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 +_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 _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; + using syncbuf_type = basic_syncbuf; + + // [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& __os, allocator_type const& __alloc) + : basic_osyncstream(__os.rdbuf(), __alloc) {} + + _LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(basic_ostream& __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 unformatte output functions. + typename basic_ostream::sentry __s(*this); + if (__s) { +# ifndef _LIBCPP_HAS_NO_EXCEPTIONS + try { +# endif // _LIBCPP_HAS_NO_EXCEPTIONS + + if (__sb_.emit() == false) + this->setstate(ios::badbit); +# ifndef _LIBCPP_HAS_NO_EXCEPTIONS + } catch (...) { + this->__set_badbit_and_consider_rethrow(); + } +# endif // _LIBCPP_HAS_NO_EXCEPTIONS + } + } + + _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(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 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP_SYNCSTREAM diff --git a/libcxx/include/version b/libcxx/include/version --- a/libcxx/include/version +++ b/libcxx/include/version @@ -401,7 +401,7 @@ # define __cpp_lib_starts_ends_with 201711L # undef __cpp_lib_string_view # define __cpp_lib_string_view 201803L -// # define __cpp_lib_syncbuf 201803L +# define __cpp_lib_syncbuf 201803L // # 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 --- a/libcxx/modules/std.cppm.in +++ b/libcxx/modules/std.cppm.in @@ -148,6 +148,7 @@ #if !defined(_LIBCPP_HAS_NO_LOCALIZATION) # include #endif +#include #include #if !defined(_LIBCPP_HAS_NO_THREADS) # include @@ -189,9 +190,6 @@ #if __has_include() # error "update the header information for in libcxx/utils/generate_std_cppm_in.py" #endif // __has_include() -#if __has_include() -# error "update the header information for in libcxx/utils/generate_std_cppm_in.py" -#endif // __has_include() #if __has_include() # error "update the header information for in libcxx/utils/generate_std_cppm_in.py" #endif // __has_include() diff --git a/libcxx/modules/std/iosfwd.inc b/libcxx/modules/std/iosfwd.inc --- a/libcxx/modules/std/iosfwd.inc +++ b/libcxx/modules/std/iosfwd.inc @@ -16,8 +16,19 @@ 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 --- a/libcxx/modules/std/syncstream.inc +++ b/libcxx/modules/std/syncstream.inc @@ -8,21 +8,19 @@ //===----------------------------------------------------------------------===// export namespace std { -#if 0 using std::basic_syncbuf; // [syncstream.syncbuf.special], specialized algorithms using std::swap; using std::syncbuf; -# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wsyncbuf; -# endif +#endif using std::basic_osyncstream; using std::osyncstream; -# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS using std::wosyncstream; -# endif #endif } // namespace std diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -858,6 +858,12 @@ strstream istream strstream ostream strstream version +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 --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -864,6 +864,12 @@ strstream istream strstream ostream strstream version +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 --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -866,6 +866,12 @@ strstream istream strstream ostream strstream version +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 --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -866,6 +866,12 @@ strstream istream strstream ostream strstream version +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 --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -871,6 +871,12 @@ strstream istream strstream ostream strstream version +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 --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -622,6 +622,12 @@ strstream istream strstream ostream strstream version +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 --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -622,6 +622,12 @@ strstream istream strstream ostream strstream version +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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/osyncstream/assign.pass.cpp @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_osyncstream; + +// basic_osyncstream& operator=(basic_osyncstream&& rhs); + +#include +#include + +#include "test_macros.h" + +template +struct test_allocator : std::allocator { + int id; + int moves = 0; + int copies = 0; + + test_allocator(int _id = 0) : id(_id) {} + test_allocator(test_allocator const& other) : id(other.id), moves(other.moves), copies(other.copies) {} + test_allocator(test_allocator&& other) + : id(std::move(other.id)), moves(std::move(other.moves)), copies(std::move(other.copies)) {} +}; + +template +class test_buf : public std::basic_streambuf {}; + +template +void test() { + using OS = std::basic_osyncstream, test_allocator>; + + test_buf base; + test_allocator alloc(42); + OS out1(&base, alloc); + typename OS::syncbuf_type* buff1 = out1.rdbuf(); + + assert(out1.get_wrapped() == &base); + assert(buff1->get_wrapped() == &base); + + static_assert(!noexcept(out1.operator=(std::move(out1)))); // LWG-3867 + OS out2 = std::move(out1); + typename OS::syncbuf_type* buff2 = out2.rdbuf(); + + assert(out2.get_wrapped() == &base); + assert(buff2->get_wrapped() == &base); + assert(buff2->get_allocator().id == alloc.id); + assert(out1.get_wrapped() == nullptr); + assert(buff1->get_wrapped() == nullptr); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/const.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/const.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/osyncstream/const.pass.cpp @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_osyncstream; + +// basic_osyncstream(streambuf_type* buf, const Allocator& allocator); +// basic_osyncstream(basic_osyncstream&& other) noexcept; + +#include +#include +#include +#include + +#include "test_macros.h" + +template +struct test_allocator : std::allocator { + int id; + int moves = 0; + int copies = 0; + + test_allocator(int _id = 0) : id(_id) {} + test_allocator(test_allocator const& other) : id(other.id), moves(other.moves), copies(other.copies) {} + test_allocator(test_allocator&& other) + : id(std::move(other.id)), moves(std::move(other.moves)), copies(std::move(other.copies)) {} +}; + +template +class test_buf : public std::basic_streambuf {}; + +template +void test_const() { + using OS = std::basic_osyncstream, test_allocator>; + + test_buf base; + test_allocator alloc(42); + OS out(&base, alloc); + typename OS::syncbuf_type* buff = out.rdbuf(); + + assert(out.get_wrapped() == &base); + assert(buff->get_wrapped() == &base); + assert(buff->get_allocator().id == alloc.id); +}; + +template +void test_rvalue() { + using OS = std::basic_osyncstream, test_allocator>; + + test_buf base; + test_allocator alloc(42); + OS out1(&base, alloc); + typename OS::syncbuf_type* buff1 = out1.rdbuf(); + + assert(out1.get_wrapped() == &base); + assert(buff1->get_wrapped() == &base); + + OS out2(std::move(out1)); + typename OS::syncbuf_type* buff2 = out2.rdbuf(); + + assert(out2.get_wrapped() == &base); + assert(buff2->get_wrapped() == &base); + assert(buff2->get_allocator().id == alloc.id); + assert(out1.get_wrapped() == nullptr); + assert(buff1->get_wrapped() == nullptr); +} + +template +void test_dest() { + using OS = std::basic_osyncstream, std::allocator>; + using SS = std::basic_ostringstream; + CharT c = 'f'; + + SS ss; + { + OS out(ss); + out << c; + assert(ss.str().empty()); + } + assert(ss.str()[0] == c); +} + +int main(int, char**) { + test_const(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_const(); +#endif + + test_rvalue(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_rvalue(); +#endif + + test_dest(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_dest(); +#endif + + return 0; +} diff --git a/libcxx/test/std/input.output/syncstream/osyncstream/members.pass.cpp b/libcxx/test/std/input.output/syncstream/osyncstream/members.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/osyncstream/members.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 + +// + +// template +// class basic_osyncstream; + +// void emit(); +// streambuf_type* get_wrapped() const noexcept; + +#include +#include +#include + +#include "test_macros.h" + +template +class test_buf : public std::basic_streambuf {}; + +template +void test_emit() { + // XXX exceptions + + using OS = std::basic_osyncstream, std::allocator>; + using SS = std::basic_ostringstream; + CharT c = 'f'; + + SS ss; + OS out(ss); + out << c; + assert(ss.str().empty()); + out.emit(); + assert(ss.str()[0] == c); +} + +template +void test_get_wrapped() { + using OS = std::basic_osyncstream, std::allocator>; + + test_buf base; + std::allocator alloc; + OS out(&base, alloc); + assert(out.get_wrapped() == &base); + ASSERT_NOEXCEPT(out.get_wrapped()); +} + +template +void test_rdbuf() { + using OS = std::basic_osyncstream, std::allocator>; + + test_buf base; + std::allocator alloc; + OS out(&base, alloc); + assert(out.rdbuf() != nullptr); + ASSERT_SAME_TYPE(decltype(out.rdbuf()), std::basic_syncbuf, std::allocator>*); + ASSERT_NOEXCEPT(out.rdbuf()); +} + +int main(int, char**) { + test_emit(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_emit(); +#endif + + test_get_wrapped(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_get_wrapped(); +#endif + + test_rdbuf(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_rdbuf(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/osyncstream/thread/basic.pass.cpp @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_osyncstream; + +// void emit(); +// streambuf_type* get_wrapped() const noexcept; + +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +using ms = std::chrono::milliseconds; +using OS = std::basic_osyncstream, std::allocator>; + +static std::basic_ostringstream ss; +static const char a = 'a'; +static const char b = 'b'; +static const char c = 'c'; +static const char d = 'd'; + +void f1() { + OS out(ss); + out << a; + std::this_thread::sleep_for(ms(250)); + out << b; +} + +void f2() { + OS out(ss); + out << c; + out << d; +} + +void test_mutlithread() { + std::thread t1(f1); + std::thread t2(f2); + t1.join(); + t2.join(); + + assert(ss.str() == "cdab"); +} + +int main(int, char**) { + // tested with char only for now + test_mutlithread(); + + 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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/osyncstream/thread/several_threads.pass.cpp @@ -0,0 +1,169 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_osyncstream; + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +using OS = std::basic_osyncstream, std::allocator>; + +static std::basic_ostringstream ss; +static std::unordered_multiset 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) { + OS out(ss); + out << text; +} + +void test() { + ss = std::basic_ostringstream(); + std::vector threads; + for (std::string const& word : test_strings) + threads.push_back(std::thread(f, word)); + for (size_t i = 0; i < test_strings.size(); (void)++i) + threads[i].join(); + + std::string output = ss.str(); + for (std::string const& word : test_strings) + assert(output.find(word) != std::string::npos); +} + +static std::basic_ostringstream ss2; + +void simple_fn() { + OS out(ss2); + out << "X"; +} + +unsigned measure_average_operation(unsigned thread_count) { + std::vector threads; + threads.reserve(thread_count); + auto start = std::chrono::high_resolution_clock::now(); + for (unsigned i = 0; i < thread_count; ++i) + threads.push_back(std::thread(simple_fn)); + for (unsigned i = 0; i < thread_count; ++i) + threads[i].join(); + auto end = std::chrono::high_resolution_clock::now(); + auto total_duration = std::chrono::duration_cast(end - start).count(); + return total_duration / thread_count; +} + +// TODO: this isn't a great test because the overhead of everything else if far more than any +// performance issues syncstream might cause. +void test_constant_performance() { + // Make sure that doing 10x the work takes no longer than 10x the time. + unsigned ten_threads = measure_average_operation(10); + unsigned hundred_threads = measure_average_operation(100); + unsigned thousand_threads = measure_average_operation(1000); + assert(ten_threads * 10 > hundred_threads); + assert(hundred_threads * 10 > thousand_threads); +} + +int main(int, char**) { + // The more we test, the more likely we catch an error + for (size_t i = 0; i < 10000; ++i) + test(); + + // Test that the complexity is linear (adding x threads takes roughly x times longer). + test_constant_performance(); + + 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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/osyncstream/types.compile.pass.cpp @@ -0,0 +1,94 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// +// +// template, class Allocator = allocator> +// class basic_osyncstream : public basic_ostream { +// 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; +// using syncbuf_type = basic_syncbuf; + +#include +#include + +#include "test_macros.h" +#include "constexpr_char_traits.h" +#include "test_allocator.h" + +static_assert(std::same_as, + std::basic_osyncstream, std::allocator>>); +static_assert(std::same_as>, + std::basic_osyncstream, std::allocator>>); +static_assert(std::same_as, test_allocator>, + std::basic_osyncstream, test_allocator>>); + +static_assert( + std::same_as, test_allocator>::char_type, char>); +static_assert(std::same_as, test_allocator>::int_type, + constexpr_char_traits::int_type>); +static_assert(std::same_as, test_allocator>::pos_type, + constexpr_char_traits::pos_type>); +static_assert(std::same_as, test_allocator>::off_type, + constexpr_char_traits::off_type>); +static_assert(std::same_as, test_allocator>::traits_type, + constexpr_char_traits>); +static_assert( + std::same_as, test_allocator>::allocator_type, + test_allocator>); +static_assert( + std::same_as, test_allocator>::streambuf_type, + std::basic_streambuf>>); +static_assert( + std::same_as, test_allocator>::syncbuf_type, + std::basic_syncbuf, test_allocator>>); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + +static_assert(std::same_as, + std::basic_osyncstream, std::allocator>>); +static_assert(std::same_as>, + std::basic_osyncstream, std::allocator>>); +static_assert(std::same_as, test_allocator>, + std::basic_osyncstream, test_allocator>>); + +static_assert( + std::same_as, test_allocator>::char_type, + wchar_t>); +static_assert( + std::same_as, test_allocator>::int_type, + constexpr_char_traits::int_type>); +static_assert( + std::same_as, test_allocator>::pos_type, + constexpr_char_traits::pos_type>); +static_assert( + std::same_as, test_allocator>::off_type, + constexpr_char_traits::off_type>); +static_assert( + std::same_as, test_allocator>::traits_type, + constexpr_char_traits>); +static_assert(std::same_as< + std::basic_osyncstream, test_allocator>::allocator_type, + test_allocator>); +static_assert(std::same_as< + std::basic_osyncstream, test_allocator>::streambuf_type, + std::basic_streambuf>>); +static_assert( + std::same_as, test_allocator>::syncbuf_type, + std::basic_syncbuf, test_allocator>>); + +#endif // TEST_HAS_NO_WIDE_CHARACTERS diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/special.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/special.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/special.pass.cpp @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// basic_syncbuf(streambuf_type* obuf, const Allocator& allocator); +// basic_syncbuf(basic_syncbuf&& other); + +#include +#include + +#include "test_macros.h" + +template +class test_streambuf : public std::basic_streambuf {}; + +template +void test() { + using BuffT = std::basic_syncbuf, std::allocator>; + + test_streambuf base1; + test_streambuf base2; + std::allocator alloc; + BuffT buff1(&base1, alloc); + BuffT buff2(&base2, alloc); + std::swap(buff1, buff2); + + assert(buff1.get_wrapped() == &base2); + assert(buff2.get_wrapped() == &base1); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} 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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/sputc.pass.cpp @@ -0,0 +1,148 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// Tests the inherited function using a custom allocator. +// +// int_type basic_streambuf::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 +#include +#include +#include + +#include "test_macros.h" +#include "test_allocator.h" + +template +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, test_allocator>; + + { // Normal + std::basic_string expected; + std::basic_stringbuf buf; + test_allocator_statistics stats; + test_allocator 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 buf; + test_allocator_statistics stats; + test_allocator 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 buf; + test_allocator_statistics stats; + test_allocator allocator{&stats}; + static_assert(!std::allocator_traits>::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 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(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif +} 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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/sputn.pass.cpp @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// Tests the inherited function using a custom allocator. +// +// streamsize basic_streambuf::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 +#include +#include +#include + +#include "test_macros.h" +#include "test_allocator.h" + +template +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 expected; + for (int i = 0; i < 1024; ++i) + expected.push_back(input[i % input.size()]); + + using SyncBuf = std::basic_syncbuf, test_allocator>; + { // Normal + std::basic_stringbuf buf; + test_allocator_statistics stats; + test_allocator 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 buf; + test_allocator_statistics stats; + test_allocator 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 buf; + test_allocator_statistics stats; + test_allocator allocator{&stats}; + static_assert(!std::allocator_traits>::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 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(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif +} 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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/assign.pass.cpp @@ -0,0 +1,401 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// basic_syncbuf& operator=(basic_syncbuf&& rhs) noexcept; + +#include +#include +#include + +#include "test_macros.h" + +template +struct test_allocator : std::allocator { + 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 test_buf : public std::basic_streambuf { +public: + int id; + + test_buf(int id = 0) : id(id) {} + + T* _pptr() { return this->pptr(); } +}; + +template > +class test_syncbuf : public std::basic_syncbuf, Alloc> { + using Base = std::basic_syncbuf, Alloc>; + +public: + test_syncbuf() = default; + + test_syncbuf(test_buf* 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++ +// inplementation details. +template +class syncbuf_inspector : public std::basic_syncbuf { +public: + syncbuf_inspector() = default; + explicit syncbuf_inspector(std::basic_syncbuf&& base) + : std::basic_syncbuf(std::move(base)) {} + + void operator=(std::basic_syncbuf&& base) { *this = std::move(base); } + + using std::basic_syncbuf::pbase; + using std::basic_syncbuf::pptr; + using std::basic_syncbuf::epptr; +}; + +template +static void test_assign() { + test_buf base; + + { // Test using the real class, propagating allocator. + using BuffT = std::basic_syncbuf, test_allocator>; + + BuffT buff1(&base, test_allocator{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(buff1.get_wrapped() == nullptr); + + assert(buff2.get_wrapped() == &base); + assert(buff2.get_allocator().id == 42); + } + + { // Test using the real class, non-propagating allocator. + using BuffT = std::basic_syncbuf, test_allocator>; + + BuffT buff1(&base, test_allocator{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(buff1.get_wrapped() == nullptr); + + 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, std::allocator>; + + using Inspector = syncbuf_inspector, std::allocator>; + 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 +static void test_basic() { + using SyncBuf = std::basic_syncbuf, std::allocator>; + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test_short_write_after_assign() { + using SyncBuf = std::basic_syncbuf, std::allocator>; + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test_long_write_after_assign() { + using SyncBuf = std::basic_syncbuf, std::allocator>; + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test_emit_on_assign() { + using SyncBuf = std::basic_syncbuf, std::allocator>; + + { // false false + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(false); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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); + } + + { // false true + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(true); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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); + } + + { // true false + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(false); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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); + } + + { // true true + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(true); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test() { + test_assign(); + test_basic(); + test_short_write_after_assign(); + test_long_write_after_assign(); + test_emit_on_assign(); +} + +int main(int, char**) { + test(); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.assign/swap.pass.cpp @@ -0,0 +1,272 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// void swap(basic_syncbuf& other) noexcept; + +#include +#include +#include + +#include "test_macros.h" + +#include + +template +static void test_basic() { + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test_short_write_after_swap() { + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test_long_write_after_swap() { + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test_emit_on_sync() { + { // false false + + std::basic_stringbuf sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(false); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(true); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(false); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 sstr1; + std::basic_stringbuf sstr2; + std::basic_string expected(42, CharT('*')); // a long string + + { + std::basic_syncbuf sync_buf1(&sstr1); + sync_buf1.set_emit_on_sync(true); + sync_buf1.sputc(CharT('A')); // a short string + + std::basic_syncbuf 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 +static void test() { + test_basic(); + test_emit_on_sync(); + test_short_write_after_swap(); + test_long_write_after_swap(); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.default.pass.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// basic_syncbuf(); + +#include +#include +#include + +#include "test_macros.h" +#include "constexpr_char_traits.h" +#include "test_allocator.h" + +template +void test() { + { + using Buf = std::basic_syncbuf; + static_assert(std::default_initializable); + Buf buf; + assert(buf.get_wrapped() == nullptr); + assert(buf.get_allocator() == std::allocator()); + } + { + using Buf = std::basic_syncbuf>; + static_assert(std::default_initializable); + Buf buf; + assert(buf.get_wrapped() == nullptr); + assert(buf.get_allocator() == std::allocator()); + } + { + using Buf = std::basic_syncbuf, test_allocator>; + static_assert(std::default_initializable); + Buf buf; + assert(buf.get_wrapped() == nullptr); + assert(buf.get_allocator() == test_allocator()); + } +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.allocator.pass.cpp @@ -0,0 +1,117 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// basic_syncbuf(streambuf_type* obuf, const Allocator&); + +#include +#include +#include + +#include "test_macros.h" +#include "constexpr_char_traits.h" +#include "test_allocator.h" + +template +void test() { + { + using Buf = std::basic_syncbuf; + std::allocator 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>; + std::allocator 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, test_allocator>; + test_allocator 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(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/cons.pointer.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 + +// + +// template +// class basic_syncbuf; + +// explicit basic_syncbuf(streambuf_type* obuf); + +#include +#include +#include + +#include "test_macros.h" +#include "constexpr_char_traits.h" +#include "test_allocator.h" + +template +void test() { + { + using Buf = std::basic_syncbuf; + static_assert(!std::convertible_to*>); + static_assert(std::constructible_from*>); + { + Buf buf{nullptr}; + assert(buf.get_wrapped() == nullptr); + assert(buf.get_allocator() == std::allocator()); + } + { + 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()); + } +#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>; + static_assert(!std::convertible_to>*>); + static_assert(std::constructible_from>*>); + { + Buf buf{nullptr}; + assert(buf.get_wrapped() == nullptr); + assert(buf.get_allocator() == std::allocator()); + } + { + 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()); + } +#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, test_allocator>; + static_assert(!std::convertible_to>*>); + static_assert(std::constructible_from>*>); + { + Buf buf{nullptr}; + assert(buf.get_wrapped() == nullptr); + assert(buf.get_allocator() == test_allocator()); + } + { + 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()); + } +#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(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/move.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/move.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.cons/move.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 + +// + +// template +// class basic_syncbuf; + +// basic_syncbuf(basic_syncbuf&& other); + +#include +#include + +#include "test_macros.h" +#include "constexpr_char_traits.h" +#include "test_allocator.h" + +template +void test() { + { + using Buf = std::basic_syncbuf; + std::allocator alloc; + { + Buf b{nullptr, alloc}; + Buf buf{std::move(b)}; + + 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 b{&w, alloc}; +#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS) + assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1); +#endif + Buf buf{std::move(b)}; +#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>; + std::allocator alloc; + { + Buf b{nullptr, alloc}; + Buf buf{std::move(b)}; + + 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 b{&w, alloc}; +#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS) + assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1); +#endif + Buf buf{std::move(b)}; +#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, test_allocator>; + test_allocator alloc{42}; + { + Buf b{nullptr, alloc}; + Buf buf{std::move(b)}; + + 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 b{&w, alloc}; +#if defined(_LIBCPP_VERSION) && !defined(TEST_HAS_NO_THREADS) + assert(std::__wrapped_streambuf_mutex::__instance().__get_count(&w) == 1); +#endif + Buf buf{std::move(b)}; +#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(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/emit.pass.cpp @@ -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 +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14 ,c++17 + +// + +// template +// class basic_syncbuf; + +// void set_emit_on_sync(bool) noexcept; + +#include +#include + +#include "test_macros.h" +#include "helpers.h" + +template +void test_set_emit_on_sync() { + // set_emit_on_sync tested in sync + test_syncbuf> buff(nullptr, std::allocator()); + ASSERT_NOEXCEPT(buff.set_emit_on_sync(false)); +} + +int main(int, char**) { + test_set_emit_on_sync(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_set_emit_on_sync(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_allocator.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 + +// + +// template +// class basic_syncbuf; + +// allocator_type get_allocator() const noexcept; + +#include +#include + +#include "test_macros.h" +#include "helpers.h" + +template +void test_get_allocator() { + test_buf base; + test_allocator alloc(42); + const test_syncbuf> buff(&base, alloc); + assert(buff.get_allocator().id == 42); + ASSERT_NOEXCEPT(buff.get_allocator()); +} + +int main(int, char**) { + test_get_allocator(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_get_allocator(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/get_wrapped.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 + +// + +// template +// class basic_syncbuf; + +// streambuf_type* get_wrapped() const noexcept; + +#include +#include + +#include "helpers.h" +#include "test_macros.h" + +template +void test() { + test_buf base(42); + std::allocator alloc; + const test_syncbuf buff(&base, alloc); + assert(static_cast*>(buff.get_wrapped())->id == 42); + ASSERT_NOEXCEPT(buff.get_wrapped()); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#endif + + return 0; +} diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/helpers.h b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/helpers.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/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 + +template +class test_buf : public std::basic_streambuf { +public: + int id; + + test_buf(int _id = 0) : id(_id) {} + + T* _pptr() { return this->pptr(); } +}; + +template > +class test_syncbuf : public std::basic_syncbuf, Alloc> { +public: + test_syncbuf(test_buf* buf, Alloc alloc) : std::basic_syncbuf, Alloc>(buf, alloc) {} + + void _setp(T* begin, T* end) { return this->setp(begin, end); } +}; + +template +struct test_allocator : std::allocator { + 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/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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.members/set_emit_on_sync.pass.cpp @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// bool emit(); + +#include +#include + +#include "helpers.h" +#include "test_macros.h" + +template +void test() { + // We do this because we want to be able to any CharT + CharT arr[3] = {'a', 'b', 'c'}; + CharT* ptr = arr; + + test_buf base; + std::allocator alloc; + test_syncbuf 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(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/syncstream.syncbuf.special/swap.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 + +// + +// template +// void swap(basic_syncbuf&, +// basic_syncbuf&); + +#include +#include + +#include "test_macros.h" + +template +void test() { + std::basic_syncbuf base1; + std::basic_syncbuf base2; + std::basic_syncbuf buff1(&base1); + std::basic_syncbuf buff2(&base2); + std::swap(buff1, buff2); + + assert(buff1.get_wrapped() == &base2); + assert(buff2.get_wrapped() == &base1); +} + +int main(int, char**) { + test(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test(); +#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 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/types.compile.pass.cpp @@ -0,0 +1,86 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template, class Allocator = allocator> +// 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; +// +// ... + +#include +#include + +#include "test_macros.h" +#include "constexpr_char_traits.h" +#include "test_allocator.h" + +static_assert( + std::same_as, std::basic_syncbuf, std::allocator>>); +static_assert(std::same_as>, + std::basic_syncbuf, std::allocator>>); +static_assert(std::same_as, test_allocator>, + std::basic_syncbuf, test_allocator>>); + +static_assert( + std::same_as, test_allocator>::char_type, char>); +static_assert(std::same_as, test_allocator>::int_type, + constexpr_char_traits::int_type>); +static_assert(std::same_as, test_allocator>::pos_type, + constexpr_char_traits::pos_type>); +static_assert(std::same_as, test_allocator>::off_type, + constexpr_char_traits::off_type>); +static_assert(std::same_as, test_allocator>::traits_type, + constexpr_char_traits>); +static_assert(std::same_as, test_allocator>::allocator_type, + test_allocator>); +static_assert(std::same_as, test_allocator>::streambuf_type, + std::basic_streambuf>>); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS +static_assert(std::same_as, + std::basic_syncbuf, std::allocator>>); +static_assert(std::same_as>, + std::basic_syncbuf, std::allocator>>); +static_assert(std::same_as, test_allocator>, + std::basic_syncbuf, test_allocator>>); + +static_assert( + std::same_as, test_allocator>::char_type, + wchar_t>); +static_assert( + std::same_as, test_allocator>::int_type, + constexpr_char_traits::int_type>); +static_assert( + std::same_as, test_allocator>::pos_type, + constexpr_char_traits::pos_type>); +static_assert( + std::same_as, test_allocator>::off_type, + constexpr_char_traits::off_type>); +static_assert( + std::same_as, test_allocator>::traits_type, + constexpr_char_traits>); +static_assert( + std::same_as, test_allocator>::allocator_type, + test_allocator>); +static_assert( + std::same_as, test_allocator>::streambuf_type, + std::basic_streambuf>>); +#endif // TEST_HAS_NO_WIDE_CHARACTERS diff --git a/libcxx/test/std/input.output/syncstream/syncbuf/virtuals.pass.cpp b/libcxx/test/std/input.output/syncstream/syncbuf/virtuals.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/syncstream/syncbuf/virtuals.pass.cpp @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// template +// class basic_syncbuf; + +// basic_syncbuf(streambuf_type* obuf, const Allocator& allocator); +// basic_syncbuf(basic_syncbuf&& other); + +#include +#include +#include +#include + +#include "test_macros.h" + +template +class test_buf : public std::basic_streambuf { +public: + T* _pbase() { return this->pbase(); } +}; + +template +class test_syncbuf : public std::basic_syncbuf, std::allocator> { +public: + test_syncbuf(test_buf* buf, std::allocator alloc) + : std::basic_syncbuf, std::allocator>(buf, alloc) {} + + int _sync() { return this->sync(); } +}; + +template +void test_sync(bool emit_on_sync) { + std::basic_ostringstream ss; + test_buf* base = reinterpret_cast*>(ss.rdbuf()); + std::allocator alloc; + test_syncbuf buff(base, alloc); + std::basic_ostream out(&buff); + + buff.set_emit_on_sync(emit_on_sync); + out << 'a'; + assert(base->_pbase() == nullptr); + assert(buff._sync() == 0); + + if (emit_on_sync) + assert(*base->_pbase() == 'a'); + else + assert(base->_pbase() == nullptr); +} + +int main(int, char**) { + test_sync(true); + test_sync(false); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_sync(true); + test_sync(false); +#endif + + return 0; +} 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 --- /dev/null +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.compile.pass.cpp @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// Test the feature test macros defined by + +/* Constant Value + __cpp_lib_syncbuf 201803L [C++20] +*/ + +#include +#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 + +# 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 + +#elif TEST_STD_VER == 23 + +# 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 + +#elif TEST_STD_VER > 23 + +# 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 + +#endif // TEST_STD_VER > 23 + diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/syncstream.version.pass.cpp @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// 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. + +// + +// Test the feature test macros defined by + +/* Constant Value + __cpp_lib_syncbuf 201803L [C++20] +*/ + +#include +#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 + +# 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 + +#elif TEST_STD_VER > 20 + +# ifndef __cpp_lib_syncbuf +# error "__cpp_lib_syncbuf should be defined in c++2b" +# endif +# if __cpp_lib_syncbuf != 201803L +# error "__cpp_lib_syncbuf should have the value 201803L in c++2b" +# endif + +#endif // TEST_STD_VER > 20 + +int main(int, char**) { return 0; } 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 --- 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,17 +3822,11 @@ # error "__cpp_lib_submdspan should not be defined before c++26" # endif -# if !defined(_LIBCPP_VERSION) -# 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 -# ifdef __cpp_lib_syncbuf -# error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!" -# endif +# 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 # ifdef __cpp_lib_text_encoding @@ -5300,17 +5294,11 @@ # error "__cpp_lib_submdspan should not be defined before c++26" # endif -# if !defined(_LIBCPP_VERSION) -# 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 -# ifdef __cpp_lib_syncbuf -# error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!" -# endif +# 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 # ifdef __cpp_lib_text_encoding @@ -6880,17 +6868,11 @@ # endif # endif -# if !defined(_LIBCPP_VERSION) -# 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 -# ifdef __cpp_lib_syncbuf -# error "__cpp_lib_syncbuf should not be defined because it is unimplemented in libc++!" -# endif +# 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 # if !defined(_LIBCPP_VERSION) diff --git a/libcxx/test/support/test_allocator.h b/libcxx/test/support/test_allocator.h --- a/libcxx/test/support/test_allocator.h +++ b/libcxx/test/support/test_allocator.h @@ -29,8 +29,9 @@ 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 @@ 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 @@ TEST_THROW(std::bad_alloc()); ++stats_->time_to_throw; ++stats_->alloc_count; + stats_->allocated_size += n; } return std::allocator().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().deallocate(p, s); } diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -1025,7 +1025,6 @@ "name": "__cpp_lib_syncbuf", "values": {"c++20": 201803}, "headers": ["syncstream"], - "unimplemented": True, }, { "name": "__cpp_lib_text_encoding", diff --git a/libcxx/utils/libcxx/header_information.py b/libcxx/utils/libcxx/header_information.py --- a/libcxx/utils/libcxx/header_information.py +++ b/libcxx/utils/libcxx/header_information.py @@ -127,7 +127,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 +156,6 @@ "spanstream", "stacktrace", "stdfloat", - "syncstream", "text_encoding", ]