mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-23 13:50:11 +00:00
7cc72a0a2e
This patch implements `std::basic_syncbuf` and `std::basic_osyncstream` as specified in paper p0053r7. ~~For ease of reviewing I am submitting this patch before submitting a patch for `std::basic_osyncstream`. ~~ ~~Please note, this patch is not 100% complete. I plan on adding more tests (see comments), specifically I plan on adding tests for multithreading and synchronization.~~ Edit: I decided that it would be far easier for me to keep track of this and make changes that affect both `std::basic_syncbuf` and `std::basic_osyncstream` if both were in one patch. The patch was originally written by @zoecarver Implements - P0053R7 - C++ Synchronized Buffered Ostream - LWG-3127 basic_osyncstream::rdbuf needs a const_cast - LWG-3334 basic_osyncstream move assignment and destruction calls basic_syncbuf::emit() twice - LWG-3570 basic_osyncstream::emit should be an unformatted output function - LWG-3867 Should std::basic_osyncstream's move assignment operator be noexcept? Reviewed By: ldionne, #libc Differential Revision: https://reviews.llvm.org/D67086
511 lines
17 KiB
C++
511 lines
17 KiB
C++
// -*- C++ -*-
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef _LIBCPP_SYNCSTREAM
|
|
#define _LIBCPP_SYNCSTREAM
|
|
|
|
/*
|
|
syncstream synopsis
|
|
|
|
#include <ostream> // see [ostream.syn]
|
|
|
|
namespace std {
|
|
template<class charT, class traits, class Allocator>
|
|
class basic_syncbuf;
|
|
|
|
// [syncstream.syncbuf.special], specialized algorithms
|
|
template<class charT, class traits, class Allocator>
|
|
void swap(basic_syncbuf<charT, traits, Allocator>&,
|
|
basic_syncbuf<charT, traits, Allocator>&);
|
|
|
|
using syncbuf = basic_syncbuf<char>;
|
|
using wsyncbuf = basic_syncbuf<wchar_t>;
|
|
|
|
template<class charT, class traits, class Allocator>
|
|
class basic_osyncstream;
|
|
|
|
using osyncstream = basic_osyncstream<char>;
|
|
using wosyncstream = basic_osyncstream<wchar_t>;
|
|
|
|
template<class charT, class traits, class Allocator>
|
|
class basic_syncbuf : public basic_streambuf<charT, traits> {
|
|
public:
|
|
using char_type = charT;
|
|
using int_type = typename traits::int_type;
|
|
using pos_type = typename traits::pos_type;
|
|
using off_type = typename traits::off_type;
|
|
using traits_type = traits;
|
|
using allocator_type = Allocator;
|
|
|
|
using streambuf_type = basic_streambuf<charT, traits>;
|
|
|
|
// [syncstream.syncbuf.cons], construction and destruction
|
|
explicit basic_syncbuf(streambuf_type* obuf = nullptr)
|
|
: basic_syncbuf(obuf, Allocator()) {}
|
|
basic_syncbuf(streambuf_type*, const Allocator&);
|
|
basic_syncbuf(basic_syncbuf&&);
|
|
~basic_syncbuf();
|
|
|
|
// [syncstream.syncbuf.assign], assignment and swap
|
|
basic_syncbuf& operator=(basic_syncbuf&&);
|
|
void swap(basic_syncbuf&);
|
|
|
|
// [syncstream.syncbuf.members], member functions
|
|
bool emit();
|
|
streambuf_type* get_wrapped() const noexcept;
|
|
allocator_type get_allocator() const noexcept;
|
|
void set_emit_on_sync(bool) noexcept;
|
|
|
|
protected:
|
|
// [syncstream.syncbuf.virtuals], overridden virtual functions
|
|
int sync() override;
|
|
|
|
private:
|
|
streambuf_type* wrapped; // exposition only
|
|
bool emit_on_sync{}; // exposition only
|
|
};
|
|
|
|
// [syncstream.syncbuf.special], specialized algorithms
|
|
template<class charT, class traits, class Allocator>
|
|
void swap(basic_syncbuf<charT, traits, Allocator>&,
|
|
basic_syncbuf<charT, traits, Allocator>&);
|
|
|
|
template<class charT, class traits, class Allocator>
|
|
class basic_osyncstream : public basic_ostream<charT, traits> {
|
|
public:
|
|
using char_type = charT;
|
|
using int_type = typename traits::int_type;
|
|
using pos_type = typename traits::pos_type;
|
|
using off_type = typename traits::off_type;
|
|
using traits_type = traits;
|
|
|
|
using allocator_type = Allocator;
|
|
using streambuf_type = basic_streambuf<charT, traits>;
|
|
using syncbuf_type = basic_syncbuf<charT, traits, Allocator>;
|
|
|
|
// [syncstream.osyncstream.cons], construction and destruction
|
|
basic_osyncstream(streambuf_type*, const Allocator&);
|
|
explicit basic_osyncstream(streambuf_type* obuf)
|
|
: basic_osyncstream(obuf, Allocator()) {}
|
|
basic_osyncstream(basic_ostream<charT, traits>& os, const Allocator& allocator)
|
|
: basic_osyncstream(os.rdbuf(), allocator) {}
|
|
explicit basic_osyncstream(basic_ostream<charT, traits>& os)
|
|
: basic_osyncstream(os, Allocator()) {}
|
|
basic_osyncstream(basic_osyncstream&&) noexcept;
|
|
~basic_osyncstream();
|
|
|
|
// [syncstream.osyncstream.assign], assignment
|
|
basic_osyncstream& operator=(basic_osyncstream&&);
|
|
|
|
// [syncstream.osyncstream.members], member functions
|
|
void emit();
|
|
streambuf_type* get_wrapped() const noexcept;
|
|
syncbuf_type* rdbuf() const noexcept { return const_cast<syncbuf_type*>(addressof(sb)); }
|
|
|
|
private:
|
|
syncbuf_type sb; // exposition only
|
|
};
|
|
}
|
|
|
|
*/
|
|
|
|
#include <__config>
|
|
#include <__utility/move.h>
|
|
#include <iosfwd> // required for declaration of default arguments
|
|
#include <string>
|
|
|
|
#ifndef _LIBCPP_HAS_NO_THREADS
|
|
# include <map>
|
|
# include <mutex>
|
|
# include <shared_mutex>
|
|
#endif
|
|
|
|
// standard-mandated includes
|
|
|
|
// [syncstream.syn]
|
|
#include <ostream>
|
|
|
|
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
|
|
# pragma GCC system_header
|
|
#endif
|
|
|
|
_LIBCPP_PUSH_MACROS
|
|
#include <__undef_macros>
|
|
|
|
_LIBCPP_BEGIN_NAMESPACE_STD
|
|
|
|
#if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
|
|
|
|
// [syncstream.syncbuf.overview]/1
|
|
// Class template basic_syncbuf stores character data written to it,
|
|
// known as the associated output, into internal buffers allocated
|
|
// using the object's allocator. The associated output is transferred
|
|
// to the wrapped stream buffer object *wrapped when emit() is called
|
|
// or when the basic_syncbuf object is destroyed. Such transfers are
|
|
// atomic with respect to transfers by other basic_syncbuf objects
|
|
// with the same wrapped stream buffer object.
|
|
//
|
|
// This helper singleton is used to implement the required
|
|
// synchronisation guarantees.
|
|
# ifndef _LIBCPP_HAS_NO_THREADS
|
|
class __wrapped_streambuf_mutex {
|
|
_LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex() = default;
|
|
|
|
public:
|
|
__wrapped_streambuf_mutex(const __wrapped_streambuf_mutex&) = delete;
|
|
__wrapped_streambuf_mutex& operator=(const __wrapped_streambuf_mutex&) = delete;
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void __inc_reference([[maybe_unused]] void* __ptr) {
|
|
_LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
|
|
unique_lock __lock{__mutex_};
|
|
++__lut_[reinterpret_cast<uintptr_t>(__ptr)].__count;
|
|
}
|
|
|
|
// pre: __ptr is in __lut_
|
|
_LIBCPP_HIDE_FROM_ABI void __dec_reference([[maybe_unused]] void* __ptr) noexcept {
|
|
unique_lock __lock{__mutex_};
|
|
|
|
auto __it = __get_it(__ptr);
|
|
if (__it->second.__count == 1)
|
|
__lut_.erase(__it);
|
|
else
|
|
--__it->second.__count;
|
|
}
|
|
|
|
// TODO
|
|
// This function causes emit() aquire two mutexes:
|
|
// - __mutex_ shared
|
|
// _ __get_it(__ptr)->second.__mutex exclusive
|
|
//
|
|
// Instead store a pointer to __get_it(__ptr)->second.__mutex when
|
|
// calling __inc_reference.
|
|
//
|
|
// pre: __ptr is in __lut_
|
|
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI lock_guard<mutex> __get_lock([[maybe_unused]] void* __ptr) noexcept {
|
|
shared_lock __lock{__mutex_};
|
|
return lock_guard{__get_it(__ptr)->second.__mutex};
|
|
}
|
|
|
|
// This function is used for testing.
|
|
//
|
|
// It is allowed to call this function with a non-registered pointer.
|
|
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI size_t __get_count([[maybe_unused]] void* __ptr) noexcept {
|
|
_LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
|
|
shared_lock __lock{__mutex_};
|
|
|
|
auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
|
|
return __it != __lut_.end() ? __it->second.__count : 0;
|
|
}
|
|
|
|
[[nodiscard]] static _LIBCPP_HIDE_FROM_ABI __wrapped_streambuf_mutex& __instance() noexcept {
|
|
static __wrapped_streambuf_mutex __result;
|
|
return __result;
|
|
}
|
|
|
|
private:
|
|
struct __value {
|
|
mutex __mutex;
|
|
size_t __count{0};
|
|
};
|
|
|
|
shared_mutex __mutex_;
|
|
map<uintptr_t, __value> __lut_;
|
|
|
|
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI map<uintptr_t, __value>::iterator __get_it(void* __ptr) noexcept {
|
|
_LIBCPP_ASSERT_INTERNAL(__ptr != nullptr, "non-wrapped streambufs are never written to");
|
|
|
|
auto __it = __lut_.find(reinterpret_cast<uintptr_t>(__ptr));
|
|
_LIBCPP_ASSERT_INTERNAL(__it != __lut_.end(), "using a wrapped streambuf that has not been registered");
|
|
_LIBCPP_ASSERT_INTERNAL(__it->second.__count >= 1, "found an inactive streambuf wrapper");
|
|
return __it;
|
|
}
|
|
};
|
|
# endif // _LIBCPP_HAS_NO_THREADS
|
|
|
|
// basic_syncbuf
|
|
|
|
// The class uses a basic_string<_CharT, _Traits, _Allocator> as
|
|
// internal buffer. Per [syncstream.syncbuf.cons]/4
|
|
// Remarks: A copy of allocator is used to allocate memory for
|
|
// internal buffers holding the associated output.
|
|
//
|
|
// Therefore the allocator used in the constructor is passed to the
|
|
// basic_string. The class does not keep a copy of this allocator.
|
|
template <class _CharT, class _Traits, class _Allocator>
|
|
class _LIBCPP_TEMPLATE_VIS basic_syncbuf : public basic_streambuf<_CharT, _Traits> {
|
|
public:
|
|
using char_type = _CharT;
|
|
using traits_type = _Traits;
|
|
using int_type = typename traits_type::int_type;
|
|
using pos_type = typename traits_type::pos_type;
|
|
using off_type = typename traits_type::off_type;
|
|
using allocator_type = _Allocator;
|
|
|
|
using streambuf_type = basic_streambuf<_CharT, _Traits>;
|
|
|
|
// [syncstream.syncbuf.cons], construction and destruction
|
|
|
|
_LIBCPP_HIDE_FROM_ABI explicit basic_syncbuf(streambuf_type* __obuf = nullptr)
|
|
: basic_syncbuf(__obuf, _Allocator()) {}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI basic_syncbuf(streambuf_type* __obuf, _Allocator const& __alloc)
|
|
: __wrapped_(__obuf), __str_(__alloc) {
|
|
__inc_reference();
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI basic_syncbuf(basic_syncbuf&& __other)
|
|
: __wrapped_(__other.get_wrapped()), __str_(std::move(__other.__str_)), __emit_on_sync_(__other.__emit_on_sync_) {
|
|
__move_common(__other);
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI ~basic_syncbuf() {
|
|
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
|
try {
|
|
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
|
emit();
|
|
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
|
} catch (...) {
|
|
}
|
|
# endif // _LIBCPP_HAS_NO_EXCEPTIONS
|
|
__dec_reference();
|
|
}
|
|
|
|
// [syncstream.syncbuf.assign], assignment and swap
|
|
|
|
_LIBCPP_HIDE_FROM_ABI basic_syncbuf& operator=(basic_syncbuf&& __other) {
|
|
// The function is specified to call emit. This call should
|
|
// propagate the exception thrown.
|
|
emit();
|
|
__dec_reference();
|
|
|
|
__wrapped_ = __other.get_wrapped();
|
|
__str_ = std::move(__other.__str_);
|
|
__emit_on_sync_ = __other.__emit_on_sync_;
|
|
|
|
__move_common(__other);
|
|
|
|
return *this;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void swap(basic_syncbuf& __other) {
|
|
_LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(
|
|
allocator_traits<_Allocator>::propagate_on_container_swap::value || get_allocator() == __other.get_allocator(),
|
|
"violates the mandated swap precondition");
|
|
|
|
basic_syncbuf __tmp(std::move(__other));
|
|
__other = std::move(*this);
|
|
*this = std::move(__tmp);
|
|
}
|
|
|
|
// [syncstream.syncbuf.members], member functions
|
|
|
|
_LIBCPP_HIDE_FROM_ABI bool emit() { return emit(false); }
|
|
|
|
_LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __wrapped_; }
|
|
|
|
_LIBCPP_HIDE_FROM_ABI allocator_type get_allocator() const noexcept { return __str_.get_allocator(); }
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void set_emit_on_sync(bool __b) noexcept { __emit_on_sync_ = __b; }
|
|
|
|
protected:
|
|
// [syncstream.syncbuf.virtuals], overridden virtual functions
|
|
|
|
_LIBCPP_HIDE_FROM_ABI_VIRTUAL
|
|
int sync() override {
|
|
if (__emit_on_sync_ && !emit(true))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI_VIRTUAL
|
|
int_type overflow(int_type __c = traits_type::eof()) override {
|
|
if (traits_type::eq_int_type(__c, traits_type::eof()))
|
|
return traits_type::not_eof(__c);
|
|
|
|
if (this->pptr() == this->epptr()) {
|
|
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
|
try {
|
|
# endif
|
|
size_t __size = __str_.size();
|
|
__str_.resize(__str_.capacity() + 1);
|
|
_LIBCPP_ASSERT_INTERNAL(__str_.size() > __size, "the buffer hasn't grown");
|
|
|
|
char_type* __p = static_cast<char_type*>(__str_.data());
|
|
this->setp(__p, __p + __str_.size());
|
|
this->pbump(__size);
|
|
|
|
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
|
} catch (...) {
|
|
return traits_type::eof();
|
|
}
|
|
# endif
|
|
}
|
|
|
|
return this->sputc(traits_type::to_char_type(__c));
|
|
}
|
|
|
|
private:
|
|
streambuf_type* __wrapped_;
|
|
|
|
// TODO Use a more generic buffer.
|
|
// That buffer should be light with almost no additional headers. Then
|
|
// it can be use here, the __retarget_buffer, and place that use
|
|
// the now deprecated get_temporary_buffer
|
|
|
|
basic_string<_CharT, _Traits, _Allocator> __str_;
|
|
bool __emit_on_sync_{false};
|
|
|
|
_LIBCPP_HIDE_FROM_ABI bool emit(bool __flush) {
|
|
if (!__wrapped_)
|
|
return false;
|
|
|
|
# ifndef _LIBCPP_HAS_NO_THREADS
|
|
lock_guard<mutex> __lock = __wrapped_streambuf_mutex::__instance().__get_lock(__wrapped_);
|
|
# endif
|
|
|
|
bool __result = true;
|
|
if (this->pptr() != this->pbase()) {
|
|
_LIBCPP_ASSERT_INTERNAL(this->pbase() && this->pptr() && this->epptr(), "all put area pointers shold be valid");
|
|
|
|
// The __str_ does not know how much of its buffer is used. This
|
|
// information is extracted from the information of the base class.
|
|
__result &= (__wrapped_->sputn(this->pbase(), this->pptr() - this->pbase()) != -1);
|
|
// Clears the buffer, but keeps the contents (and) size of the
|
|
// internal buffer.
|
|
this->setp(this->pbase(), this->epptr());
|
|
}
|
|
|
|
if (__flush)
|
|
__result &= (__wrapped_->pubsync() != -1);
|
|
|
|
return __result;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void __move_common(basic_syncbuf& __other) {
|
|
// Adjust the put area pointers to our buffer.
|
|
char_type* __p = static_cast<char_type*>(__str_.data());
|
|
this->setp(__p, __p + __str_.size());
|
|
this->pbump(__other.pptr() - __other.pbase());
|
|
|
|
// Clear __other_ so the destructor will act as a NOP.
|
|
__other.setp(nullptr, nullptr);
|
|
__other.__wrapped_ = nullptr;
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void __inc_reference() {
|
|
# ifndef _LIBCPP_HAS_NO_THREADS
|
|
if (__wrapped_)
|
|
__wrapped_streambuf_mutex::__instance().__inc_reference(__wrapped_);
|
|
# endif
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void __dec_reference() noexcept {
|
|
# ifndef _LIBCPP_HAS_NO_THREADS
|
|
if (__wrapped_)
|
|
__wrapped_streambuf_mutex::__instance().__dec_reference(__wrapped_);
|
|
# endif
|
|
}
|
|
};
|
|
|
|
using std::syncbuf;
|
|
# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
|
|
using std::wsyncbuf;
|
|
# endif
|
|
|
|
// [syncstream.syncbuf.special], specialized algorithms
|
|
template <class _CharT, class _Traits, class _Allocator>
|
|
_LIBCPP_HIDE_FROM_ABI void
|
|
swap(basic_syncbuf<_CharT, _Traits, _Allocator>& __lhs, basic_syncbuf<_CharT, _Traits, _Allocator>& __rhs) {
|
|
__lhs.swap(__rhs);
|
|
}
|
|
|
|
// basic_osyncstream
|
|
|
|
template <class _CharT, class _Traits, class _Allocator>
|
|
class _LIBCPP_TEMPLATE_VIS basic_osyncstream : public basic_ostream<_CharT, _Traits> {
|
|
public:
|
|
using char_type = _CharT;
|
|
using traits_type = _Traits;
|
|
using int_type = typename traits_type::int_type;
|
|
using pos_type = typename traits_type::pos_type;
|
|
using off_type = typename traits_type::off_type;
|
|
|
|
using allocator_type = _Allocator;
|
|
using streambuf_type = basic_streambuf<char_type, traits_type>;
|
|
using syncbuf_type = basic_syncbuf<char_type, traits_type, allocator_type>;
|
|
|
|
// [syncstream.osyncstream.cons], construction and destruction
|
|
|
|
_LIBCPP_HIDE_FROM_ABI basic_osyncstream(streambuf_type* __obuf, allocator_type const& __alloc)
|
|
: basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(__obuf, __alloc) {}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(streambuf_type* __obuf)
|
|
: basic_osyncstream(__obuf, allocator_type()) {}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_ostream<char_type, traits_type>& __os, allocator_type const& __alloc)
|
|
: basic_osyncstream(__os.rdbuf(), __alloc) {}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI explicit basic_osyncstream(basic_ostream<char_type, traits_type>& __os)
|
|
: basic_osyncstream(__os, allocator_type()) {}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI basic_osyncstream(basic_osyncstream&& __other) noexcept
|
|
: basic_ostream<_CharT, _Traits>(std::addressof(__sb_)), __sb_(std::move(__other.__sb_)) {
|
|
this->set_rdbuf(std::addressof(__sb_));
|
|
}
|
|
|
|
// [syncstream.osyncstream.assign], assignment
|
|
|
|
_LIBCPP_HIDE_FROM_ABI basic_osyncstream& operator=(basic_osyncstream&& __other) = default;
|
|
|
|
// [syncstream.osyncstream.members], member functions
|
|
|
|
_LIBCPP_HIDE_FROM_ABI void emit() {
|
|
// The basic_ostream::put places the sentry in a try
|
|
// catch, this does not match the wording of the standard
|
|
// [ostream.unformatted]
|
|
// TODO validate other unformatted output functions.
|
|
typename basic_ostream<char_type, traits_type>::sentry __s(*this);
|
|
if (__s) {
|
|
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
|
try {
|
|
# endif
|
|
|
|
if (__sb_.emit() == false)
|
|
this->setstate(ios::badbit);
|
|
# ifndef _LIBCPP_HAS_NO_EXCEPTIONS
|
|
} catch (...) {
|
|
this->__set_badbit_and_consider_rethrow();
|
|
}
|
|
# endif
|
|
}
|
|
}
|
|
|
|
_LIBCPP_HIDE_FROM_ABI streambuf_type* get_wrapped() const noexcept { return __sb_.get_wrapped(); }
|
|
|
|
_LIBCPP_HIDE_FROM_ABI syncbuf_type* rdbuf() const noexcept {
|
|
return const_cast<syncbuf_type*>(std::addressof(__sb_));
|
|
}
|
|
|
|
private:
|
|
syncbuf_type __sb_;
|
|
};
|
|
|
|
using std::osyncstream;
|
|
# ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS
|
|
using std::wosyncstream;
|
|
# endif
|
|
|
|
#endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_SYNCSTREAM)
|
|
|
|
_LIBCPP_END_NAMESPACE_STD
|
|
|
|
_LIBCPP_POP_MACROS
|
|
|
|
#endif // _LIBCPP_SYNCSTREAM
|