diff --git a/libcxx/include/sstream b/libcxx/include/sstream --- a/libcxx/include/sstream +++ b/libcxx/include/sstream @@ -399,12 +399,12 @@ _LIBCPP_HIDE_FROM_ABI_SSTREAM string_type str() const & { return str(__str_.get_allocator()); } _LIBCPP_HIDE_FROM_ABI_SSTREAM string_type str() && { - string_type __result; const basic_string_view<_CharT, _Traits> __view = view(); - if (!__view.empty()) { - auto __pos = __view.data() - __str_.data(); - __result.assign(std::move(__str_), __pos, __view.size()); - } + typename string_type::size_type __pos = __view.empty() ? 0 : __view.data() - __str_.data(); + // In C++23, this is just string_type(std::move(__str_), __pos, __view.size(), __str_.get_allocator()); + // But we need something that works in C++20 also. + string_type __result(__str_.get_allocator()); + __result.__pilfering_assign(std::move(__str_), __pos, __view.size()); __str_.clear(); __init_buf_ptrs(); return __result; diff --git a/libcxx/include/string b/libcxx/include/string --- a/libcxx/include/string +++ b/libcxx/include/string @@ -970,7 +970,9 @@ __init(__n, __c); } -#if _LIBCPP_STD_VER >= 23 +#if _LIBCPP_STD_VER >= 20 + // Extension: Support these overloads in C++20 rather than C++23. + // Otherwise C++20's `basic_stringbuf::str() &&` is hard to implement. _LIBCPP_HIDE_FROM_ABI constexpr basic_string(basic_string&& __str, size_type __pos, const _Allocator& __alloc = _Allocator()) : basic_string(std::move(__str), __pos, npos, __alloc) {} @@ -983,12 +985,7 @@ auto __len = std::min(__n, __str.size() - __pos); if (__alloc_traits::is_always_equal::value || __alloc == __str.__alloc()) { - __r_.first() = __str.__r_.first(); - __str.__default_init(); - - _Traits::move(data(), data() + __pos, __len); - __set_size(__len); - _Traits::assign(data()[__len], value_type()); + __pilfering_assign(std::move(__str), __pos, __len); } else { // Perform a copy because the allocators are not compatible. __init(__str.data() + __pos, __len); @@ -1333,6 +1330,20 @@ return assign(__sv.data(), __sv.size()); } +#if _LIBCPP_STD_VER >= 20 + _LIBCPP_HIDE_FROM_ABI constexpr + void __pilfering_assign(basic_string&& __str, size_type __pos, size_type __len) { + // Pilfer the allocation from __str. + // Precondition: __alloc == __str.__alloc() + __r_.first() = __str.__r_.first(); + __str.__default_init(); + + _Traits::move(data(), data() + __pos, __len); + __set_size(__len); + _Traits::assign(data()[__len], value_type()); + } +#endif + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 basic_string& assign(const basic_string& __str) { return *this = __str; } #ifndef _LIBCPP_CXX03_LANG diff --git a/libcxx/test/std/input.output/string.streams/istringstream/istringstream.members/str.allocator_propagation.pass.cpp b/libcxx/test/std/input.output/string.streams/istringstream/istringstream.members/str.allocator_propagation.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/string.streams/istringstream/istringstream.members/str.allocator_propagation.pass.cpp @@ -0,0 +1,165 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// TODO: Change to XFAIL once https://github.com/llvm/llvm-project/issues/40340 is fixed +// UNSUPPORTED: availability-pmr-missing + +// This test ensures that we properly propagate allocators from istringstream's +// inner string object to the new string returned from .str(). +// `str() const&` is specified to preserve the allocator (not copy the string). +// `str() &&` isn't specified, but should preserve the allocator (move the string). + +#include +#include +#include +#include +#include +#include +#include + +#include "make_string.h" +#include "test_macros.h" + +template +struct SocccAllocator { + using value_type = T; + + int count_ = 0; + explicit SocccAllocator(int i) : count_(i) {} + + template + SocccAllocator(const SocccAllocator& a) : count_(a.count_) {} + + T* allocate(std::size_t n) { return std::allocator().allocate(n); } + void deallocate(T* p, std::size_t n) { std::allocator().deallocate(p, n); } + + SocccAllocator select_on_container_copy_construction() const { + return SocccAllocator(count_ + 1); + } + + bool operator==(const SocccAllocator&) const { return true; } + + using propagate_on_container_copy_assignment = std::false_type; + using propagate_on_container_move_assignment = std::false_type; + using propagate_on_container_swap = std::false_type; +}; + +template +void test_soccc_behavior() +{ + using Alloc = SocccAllocator; + using SS = std::basic_istringstream, Alloc>; + using S = std::basic_string, Alloc>; + { + SS ss = SS(std::ios_base::in, Alloc(10)); + + // [stringbuf.members]/6 specifies that the allocator is copied, + // not select_on_container_copy_construction'ed. + // + S copied = ss.str(); + assert(copied.get_allocator().count_ == 10); + assert(ss.rdbuf()->get_allocator().count_ == 10); + assert(copied.empty()); + + assert(S(copied).get_allocator().count_ == 11); + // sanity-check that SOCCC does in fact work + + // [stringbuf.members]/10 doesn't specify the allocator to use, + // but copying the allocator as-if-by moving the string makes sense. + // + S moved = std::move(ss).str(); + assert(moved.get_allocator().count_ == 10); + assert(ss.rdbuf()->get_allocator().count_ == 10); + assert(moved.empty()); + } +} + +template, std::pmr::polymorphic_allocator>> +struct StringBuf : Base { + explicit StringBuf(std::pmr::memory_resource *mr) : Base(std::ios_base::in, mr) {} + void public_setg(int a, int b, int c) { + CharT *p = this->eback(); + assert(this->view().data() == p); + this->setg(p + a, p + b, p + c); + assert(this->eback() == p + a); + assert(this->view().data() == p + a); + } +}; + +template +void test_allocation_is_pilfered() { + using SS = std::basic_istringstream, std::pmr::polymorphic_allocator>; + using S = std::pmr::basic_string; + alignas(void*) char buf[80 * sizeof(CharT)]; + const CharT *initial = MAKE_CSTRING(CharT, "a very long string that exceeds the small string optimization buffer length"); + { + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(buf, sizeof(buf), std::pmr::null_memory_resource()); + SS ss = SS(S(initial, &mr1)); + S s = std::move(ss).str(); + assert(s == initial); + } + { + // Try moving-out-of a stringbuf whose view() is not the entire string. + // This is libc++'s behavior; libstdc++ doesn't allow such stringbufs to be created. + // + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(buf, sizeof(buf), std::pmr::null_memory_resource()); + auto src = StringBuf(&mr1); + src.str(S(initial, &mr1)); + src.public_setg(2, 6, 40); + SS ss(std::ios_base::in, &mr1); + *ss.rdbuf() = std::move(src); + LIBCPP_ASSERT(ss.view() == std::basic_string_view(initial).substr(2, 38)); + S s = std::move(ss).str(); + LIBCPP_ASSERT(s == std::basic_string_view(initial).substr(2, 38)); + } +} + +template +void test_no_foreign_allocations() { + using SS = std::basic_istringstream, std::pmr::polymorphic_allocator>; + using S = std::pmr::basic_string; + const CharT *initial = MAKE_CSTRING(CharT, "a very long string that exceeds the small string optimization buffer length"); + { + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(std::pmr::new_delete_resource()); + auto ss = SS(S(initial, &mr1)); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + + // [stringbuf.members]/6 specifies that the result of `str() const &` + // does NOT use the default allocator; it uses the original allocator. + // + S copied = ss.str(); + assert(copied.get_allocator().resource() == &mr1); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + assert(copied == initial); + + // [stringbuf.members]/10 doesn't specify the allocator to use, + // but copying the allocator as-if-by moving the string makes sense. + // + S moved = std::move(ss).str(); + assert(moved.get_allocator().resource() == &mr1); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + assert(moved == initial); + } +} + +int main(int, char**) { + test_soccc_behavior(); + test_allocation_is_pilfered(); + test_no_foreign_allocations(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_soccc_behavior(); + test_allocation_is_pilfered(); + test_no_foreign_allocations(); +#endif + + return 0; +} diff --git a/libcxx/test/std/input.output/string.streams/istringstream/istringstream.members/str.move.pass.cpp b/libcxx/test/std/input.output/string.streams/istringstream/istringstream.members/str.move.pass.cpp --- a/libcxx/test/std/input.output/string.streams/istringstream/istringstream.members/str.move.pass.cpp +++ b/libcxx/test/std/input.output/string.streams/istringstream/istringstream.members/str.move.pass.cpp @@ -37,6 +37,13 @@ assert(s.empty()); assert(ss.view().empty()); } + { + std::basic_istringstream ss(STR("a very long string that exceeds the small string optimization buffer length")); + const CharT *p = ss.view().data(); + std::basic_string s = std::move(ss).str(); + assert(s.data() == p); // the allocation was pilfered + assert(ss.view().empty()); + } } int main(int, char**) { diff --git a/libcxx/test/std/input.output/string.streams/ostringstream/ostringstream.members/str.allocator_propagation.pass.cpp b/libcxx/test/std/input.output/string.streams/ostringstream/ostringstream.members/str.allocator_propagation.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/string.streams/ostringstream/ostringstream.members/str.allocator_propagation.pass.cpp @@ -0,0 +1,137 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// TODO: Change to XFAIL once https://github.com/llvm/llvm-project/issues/40340 is fixed +// UNSUPPORTED: availability-pmr-missing + +// This test ensures that we properly propagate allocators from ostringstream's +// inner string object to the new string returned from .str(). +// `str() const&` is specified to preserve the allocator (not copy the string). +// `str() &&` isn't specified, but should preserve the allocator (move the string). + +#include +#include +#include +#include +#include +#include + +#include "make_string.h" +#include "test_macros.h" + +template +struct SocccAllocator { + using value_type = T; + + int count_ = 0; + explicit SocccAllocator(int i) : count_(i) {} + + template + SocccAllocator(const SocccAllocator& a) : count_(a.count_) {} + + T* allocate(std::size_t n) { return std::allocator().allocate(n); } + void deallocate(T* p, std::size_t n) { std::allocator().deallocate(p, n); } + + SocccAllocator select_on_container_copy_construction() const { + return SocccAllocator(count_ + 1); + } + + bool operator==(const SocccAllocator&) const { return true; } + + using propagate_on_container_copy_assignment = std::false_type; + using propagate_on_container_move_assignment = std::false_type; + using propagate_on_container_swap = std::false_type; +}; + +template +void test_soccc_behavior() +{ + using Alloc = SocccAllocator; + using SS = std::basic_ostringstream, Alloc>; + using S = std::basic_string, Alloc>; + { + SS ss = SS(std::ios_base::out, Alloc(10)); + + // [stringbuf.members]/6 specifies that the allocator is copied, + // not select_on_container_copy_construction'ed. + // + S copied = ss.str(); + assert(copied.get_allocator().count_ == 10); + assert(ss.rdbuf()->get_allocator().count_ == 10); + assert(copied.empty()); + + assert(S(copied).get_allocator().count_ == 11); + // sanity-check that SOCCC does in fact work + + // [stringbuf.members]/10 doesn't specify the allocator to use, + // but copying the allocator as-if-by moving the string makes sense. + // + S moved = std::move(ss).str(); + assert(moved.get_allocator().count_ == 10); + assert(ss.rdbuf()->get_allocator().count_ == 10); + assert(moved.empty()); + } +} + +template +void test_allocation_is_pilfered() { + using SS = std::basic_ostringstream, std::pmr::polymorphic_allocator>; + using S = std::pmr::basic_string; + alignas(void*) char buf[80 * sizeof(CharT)]; + const CharT *initial = MAKE_CSTRING(CharT, "a very long string that exceeds the small string optimization buffer length"); + { + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(buf, sizeof(buf), std::pmr::null_memory_resource()); + SS ss = SS(S(initial, &mr1)); + S s = std::move(ss).str(); + assert(s == initial); + } +} + +template +void test_no_foreign_allocations() { + using SS = std::basic_ostringstream, std::pmr::polymorphic_allocator>; + using S = std::pmr::basic_string; + const CharT *initial = MAKE_CSTRING(CharT, "a very long string that exceeds the small string optimization buffer length"); + { + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(std::pmr::new_delete_resource()); + auto ss = SS(S(initial, &mr1)); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + + // [stringbuf.members]/6 specifies that the result of `str() const &` + // does NOT use the default allocator; it uses the original allocator. + // + S copied = ss.str(); + assert(copied.get_allocator().resource() == &mr1); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + assert(copied == initial); + + // [stringbuf.members]/10 doesn't specify the allocator to use, + // but copying the allocator as-if-by moving the string makes sense. + // + S moved = std::move(ss).str(); + assert(moved.get_allocator().resource() == &mr1); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + assert(moved == initial); + } +} + +int main(int, char**) { + test_soccc_behavior(); + test_allocation_is_pilfered(); + test_no_foreign_allocations(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_soccc_behavior(); + test_allocation_is_pilfered(); + test_no_foreign_allocations(); +#endif + + return 0; +} diff --git a/libcxx/test/std/input.output/string.streams/ostringstream/ostringstream.members/str.move.pass.cpp b/libcxx/test/std/input.output/string.streams/ostringstream/ostringstream.members/str.move.pass.cpp --- a/libcxx/test/std/input.output/string.streams/ostringstream/ostringstream.members/str.move.pass.cpp +++ b/libcxx/test/std/input.output/string.streams/ostringstream/ostringstream.members/str.move.pass.cpp @@ -37,6 +37,13 @@ assert(s.empty()); assert(ss.view().empty()); } + { + std::basic_ostringstream ss(STR("a very long string that exceeds the small string optimization buffer length")); + const CharT *p = ss.view().data(); + std::basic_string s = std::move(ss).str(); + assert(s.data() == p); // the allocation was pilfered + assert(ss.view().empty()); + } } int main(int, char**) { diff --git a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.move.pass.cpp b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.move.pass.cpp --- a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.move.pass.cpp +++ b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.move.pass.cpp @@ -37,6 +37,48 @@ assert(s.empty()); assert(buf.view().empty()); } + { + std::basic_stringbuf buf(STR("a very long string that exceeds the small string optimization buffer length")); + const CharT *p = buf.view().data(); + std::basic_string s = std::move(buf).str(); + assert(s.data() == p); // the allocation was pilfered + assert(buf.view().empty()); + } +} + +struct StringBuf : std::stringbuf { + using basic_stringbuf::basic_stringbuf; + void public_setg(int a, int b, int c) { + char *p = eback(); + this->setg(p + a, p + b, p + c); + } +}; + +static void test_altered_sequence_pointers() { + { + auto src = StringBuf("hello world", std::ios_base::in); + src.public_setg(4, 6, 9); + std::stringbuf dest; + dest = std::move(src); + std::string view = std::string(dest.view()); + std::string str = std::move(dest).str(); + assert(view == str); + LIBCPP_ASSERT(str == "o wor"); + assert(dest.str().empty()); + assert(dest.view().empty()); + } + { + auto src = StringBuf("hello world", std::ios_base::in); + src.public_setg(4, 6, 9); + std::stringbuf dest; + dest.swap(src); + std::string view = std::string(dest.view()); + std::string str = std::move(dest).str(); + assert(view == str); + LIBCPP_ASSERT(str == "o wor"); + assert(dest.str().empty()); + assert(dest.view().empty()); + } } int main(int, char**) { @@ -44,5 +86,6 @@ #ifndef TEST_HAS_NO_WIDE_CHARACTERS test(); #endif + test_altered_sequence_pointers(); return 0; } diff --git a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.pass.cpp b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.pass.cpp --- a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.pass.cpp +++ b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/str.pass.cpp @@ -14,12 +14,46 @@ // void str(const basic_string& s); #include +#include #include #include "test_macros.h" +struct StringBuf : std::stringbuf { + explicit StringBuf(const char *s, std::ios_base::openmode mode) : + basic_stringbuf(s, mode) { } + void public_setg(int a, int b, int c) { + char *p = eback(); + this->setg(p + a, p + b, p + c); + } +}; + +static void test_altered_sequence_pointers() { + { + StringBuf src("hello world", std::ios_base::in); + src.public_setg(4, 6, 9); + std::stringbuf dest; + dest = std::move(src); + std::string str = dest.str(); + assert(5 <= str.size() && str.size() <= 11); + LIBCPP_ASSERT(str == "o wor"); + LIBCPP_ASSERT(dest.str() == "o wor"); + } + { + StringBuf src("hello world", std::ios_base::in); + src.public_setg(4, 6, 9); + std::stringbuf dest; + dest.swap(src); + std::string str = dest.str(); + assert(5 <= str.size() && str.size() <= 11); + LIBCPP_ASSERT(str == "o wor"); + LIBCPP_ASSERT(dest.str() == "o wor"); + } +} + int main(int, char**) { + test_altered_sequence_pointers(); { std::stringbuf buf("testing"); assert(buf.str() == "testing"); diff --git a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/view.pass.cpp b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/view.pass.cpp --- a/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/view.pass.cpp +++ b/libcxx/test/std/input.output/string.streams/stringbuf/stringbuf.members/view.pass.cpp @@ -50,10 +50,38 @@ static_assert(std::is_same_v>>); } +struct StringBuf : std::stringbuf { + using basic_stringbuf::basic_stringbuf; + void public_setg(int a, int b, int c) { + char *p = eback(); + this->setg(p + a, p + b, p + c); + } +}; + +static void test_altered_sequence_pointers() { + { + auto src = StringBuf("hello world", std::ios_base::in); + src.public_setg(4, 6, 9); + std::stringbuf dest; + dest = std::move(src); + assert(dest.view() == dest.str()); + LIBCPP_ASSERT(dest.view() == "o wor"); + } + { + auto src = StringBuf("hello world", std::ios_base::in); + src.public_setg(4, 6, 9); + std::stringbuf dest; + dest.swap(src); + assert(dest.view() == dest.str()); + LIBCPP_ASSERT(dest.view() == "o wor"); + } +} + int main(int, char**) { test(); #ifndef TEST_HAS_NO_WIDE_CHARACTERS test(); #endif + test_altered_sequence_pointers(); return 0; } diff --git a/libcxx/test/std/input.output/string.streams/stringstream/stringstream.members/str.allocator_propagation.pass.cpp b/libcxx/test/std/input.output/string.streams/stringstream/stringstream.members/str.allocator_propagation.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/input.output/string.streams/stringstream/stringstream.members/str.allocator_propagation.pass.cpp @@ -0,0 +1,165 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// TODO: Change to XFAIL once https://github.com/llvm/llvm-project/issues/40340 is fixed +// UNSUPPORTED: availability-pmr-missing + +// This test ensures that we properly propagate allocators from stringstream's +// inner string object to the new string returned from .str(). +// `str() const&` is specified to preserve the allocator (not copy the string). +// `str() &&` isn't specified, but should preserve the allocator (move the string). + +#include +#include +#include +#include +#include +#include +#include + +#include "make_string.h" +#include "test_macros.h" + +template +struct SocccAllocator { + using value_type = T; + + int count_ = 0; + explicit SocccAllocator(int i) : count_(i) {} + + template + SocccAllocator(const SocccAllocator& a) : count_(a.count_) {} + + T* allocate(std::size_t n) { return std::allocator().allocate(n); } + void deallocate(T* p, std::size_t n) { std::allocator().deallocate(p, n); } + + SocccAllocator select_on_container_copy_construction() const { + return SocccAllocator(count_ + 1); + } + + bool operator==(const SocccAllocator&) const { return true; } + + using propagate_on_container_copy_assignment = std::false_type; + using propagate_on_container_move_assignment = std::false_type; + using propagate_on_container_swap = std::false_type; +}; + +template +void test_soccc_behavior() +{ + using Alloc = SocccAllocator; + using SS = std::basic_stringstream, Alloc>; + using S = std::basic_string, Alloc>; + { + SS ss = SS(std::ios_base::out, Alloc(10)); + + // [stringbuf.members]/6 specifies that the allocator is copied, + // not select_on_container_copy_construction'ed. + // + S copied = ss.str(); + assert(copied.get_allocator().count_ == 10); + assert(ss.rdbuf()->get_allocator().count_ == 10); + assert(copied.empty()); + + assert(S(copied).get_allocator().count_ == 11); + // sanity-check that SOCCC does in fact work + + // [stringbuf.members]/10 doesn't specify the allocator to use, + // but copying the allocator as-if-by moving the string makes sense. + // + S moved = std::move(ss).str(); + assert(moved.get_allocator().count_ == 10); + assert(ss.rdbuf()->get_allocator().count_ == 10); + assert(moved.empty()); + } +} + +template, std::pmr::polymorphic_allocator>> +struct StringBuf : Base { + explicit StringBuf(std::pmr::memory_resource *mr) : Base(std::ios_base::in, mr) {} + void public_setg(int a, int b, int c) { + CharT *p = this->eback(); + assert(this->view().data() == p); + this->setg(p + a, p + b, p + c); + assert(this->eback() == p + a); + assert(this->view().data() == p + a); + } +}; + +template +void test_allocation_is_pilfered() { + using SS = std::basic_stringstream, std::pmr::polymorphic_allocator>; + using S = std::pmr::basic_string; + alignas(void*) char buf[80 * sizeof(CharT)]; + const CharT *initial = MAKE_CSTRING(CharT, "a very long string that exceeds the small string optimization buffer length"); + { + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(buf, sizeof(buf), std::pmr::null_memory_resource()); + SS ss = SS(S(initial, &mr1)); + S s = std::move(ss).str(); + assert(s == initial); + } + { + // Try moving-out-of a stringbuf whose view() is not the entire string. + // This is libc++'s behavior; libstdc++ doesn't allow such stringbufs to be created. + // + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(buf, sizeof(buf), std::pmr::null_memory_resource()); + auto src = StringBuf(&mr1); + src.str(S(initial, &mr1)); + src.public_setg(2, 6, 40); + SS ss(std::ios_base::in, &mr1); + *ss.rdbuf() = std::move(src); + LIBCPP_ASSERT(ss.view() == std::basic_string_view(initial).substr(2, 38)); + S s = std::move(ss).str(); + LIBCPP_ASSERT(s == std::basic_string_view(initial).substr(2, 38)); + } +} + +template +void test_no_foreign_allocations() { + using SS = std::basic_stringstream, std::pmr::polymorphic_allocator>; + using S = std::pmr::basic_string; + const CharT *initial = MAKE_CSTRING(CharT, "a very long string that exceeds the small string optimization buffer length"); + { + std::pmr::set_default_resource(std::pmr::null_memory_resource()); + auto mr1 = std::pmr::monotonic_buffer_resource(std::pmr::new_delete_resource()); + auto ss = SS(S(initial, &mr1)); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + + // [stringbuf.members]/6 specifies that the result of `str() const &` + // does NOT use the default allocator; it uses the original allocator. + // + S copied = ss.str(); + assert(copied.get_allocator().resource() == &mr1); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + assert(copied == initial); + + // [stringbuf.members]/10 doesn't specify the allocator to use, + // but copying the allocator as-if-by moving the string makes sense. + // + S moved = std::move(ss).str(); + assert(moved.get_allocator().resource() == &mr1); + assert(ss.rdbuf()->get_allocator().resource() == &mr1); + assert(moved == initial); + } +} + +int main(int, char**) { + test_soccc_behavior(); + test_allocation_is_pilfered(); + test_no_foreign_allocations(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_soccc_behavior(); + test_allocation_is_pilfered(); + test_no_foreign_allocations(); +#endif + + return 0; +} diff --git a/libcxx/test/std/input.output/string.streams/stringstream/stringstream.members/str.move.pass.cpp b/libcxx/test/std/input.output/string.streams/stringstream/stringstream.members/str.move.pass.cpp --- a/libcxx/test/std/input.output/string.streams/stringstream/stringstream.members/str.move.pass.cpp +++ b/libcxx/test/std/input.output/string.streams/stringstream/stringstream.members/str.move.pass.cpp @@ -37,6 +37,13 @@ assert(s.empty()); assert(ss.view().empty()); } + { + std::basic_stringstream ss(STR("a very long string that exceeds the small string optimization buffer length")); + const CharT *p = ss.view().data(); + std::basic_string s = std::move(ss).str(); + assert(s.data() == p); // the allocation was pilfered + assert(ss.view().empty()); + } } int main(int, char**) {