diff --git a/libcxx/include/sstream b/libcxx/include/sstream --- a/libcxx/include/sstream +++ b/libcxx/include/sstream @@ -399,12 +399,10 @@ _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()); - } + auto __pos = __view.empty() ? 0 : __view.data() - __str_.data(); + string_type __result(std::move(__str_), __pos, __view.size(), __str_.get_allocator()); + __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) {} 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,163 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// 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 SOCCC'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,135 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// 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 SOCCC'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,46 @@ 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.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.view().empty()); + } } int main(int, char**) { @@ -44,5 +84,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"); + 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"); + 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,163 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// 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 SOCCC'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**) {