diff --git a/libcxx/include/string b/libcxx/include/string --- a/libcxx/include/string +++ b/libcxx/include/string @@ -684,6 +684,9 @@ "traits_type::char_type must be the same type as CharT"); static_assert(( is_same::value), "Allocator::value_type must be same type as value_type"); +#if _LIBCPP_STD_VER < 20 + static_assert((is_trivially_destructible::value), "pointer type must have a trivial destructor."); +#endif typedef __wrap_iter iterator; typedef __wrap_iter const_iterator; @@ -772,7 +775,20 @@ static_assert(sizeof(__short) == (sizeof(value_type) * (__min_cap + 1)), "__short has an unexpected size."); +#ifdef _LIBCPP_CXX03_LANG union __ulx{__long __lx; __short __lxx;}; +#else + // Since C++03, unions can contain members with non-trivial special member functions. The corresponding special + // member functions of the union will be deleted and therefore must be explicitly defined. To support fancy pointers + // with non-trivial constructors in __long, we must explicitly define a constructor in __ulx, as the default + // constructor will be deleted. + union __ulx { + _LIBCPP_CONSTEXPR_SINCE_CXX20 __ulx() _NOEXCEPT : __lx{} {} + + __long __lx; + __short __lxx; + }; +#endif // _LIBCPP_CXX03_LANG enum {__n_words = sizeof(__ulx) / sizeof(size_type)}; @@ -781,6 +797,7 @@ size_type __words[__n_words]; }; +#ifdef _LIBCPP_CXX03_LANG struct __rep { union @@ -790,6 +807,67 @@ __raw __r; }; }; +#else + // Since C++03, unions can contain members with non-trivial special member functions. The corresponding special + // member functions of the union will be deleted and therefore must be explicitly defined. To support fancy pointers + // with non-trivial special member functions in __long, we must explicitly define a constructor, copy constructor + // and copy assignment operator in __rep. Since C++20, unions can also contain members with non-trivial destructors. + struct __rep { + + _LIBCPP_CONSTEXPR_SINCE_CXX20 __rep() _NOEXCEPT : __l{} {} + _LIBCPP_CONSTEXPR_SINCE_CXX20 ~__rep() _NOEXCEPT {} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 + bool __is_rep_long(const __rep& __str_rep) const _NOEXCEPT { + if (__libcpp_is_constant_evaluated()) + return true; + return __str_rep.__l.__is_long_; + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 __rep(const __rep& __other) _NOEXCEPT : __l{} { + // For certain fancy pointers such as offset pointers, the value of __l.__data_ will be different for 2 pointers + // pointing to the same object (because the offset is calculated with respect to the pointer itself). Therefore, + // the copy assignment operator of the underlying pointer needs to be explicitly called. For a short string, the + // memory should be directly copied. + if (__is_rep_long(__other)) + { + __l.__is_long_ = __other.__l.__is_long_; + __l.__cap_ = __other.__l.__cap_; + __l.__size_ = __other.__l.__size_; + __l.__data_ = __other.__l.__data_; + } + else + { + for (unsigned __i = 0; __i < __n_words; ++__i) + __r.__words[__i] = __other.__r.__words[__i]; + } + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 __rep& operator=(const __rep& __other) _NOEXCEPT { + if (this != &__other) { + if (__is_rep_long(__other)) + { + __l.__is_long_ = __other.__l.__is_long_; + __l.__cap_ = __other.__l.__cap_; + __l.__size_ = __other.__l.__size_; + __l.__data_ = __other.__l.__data_; + } + else + { + for (unsigned __i = 0; __i < __n_words; ++__i) + __r.__words[__i] = __other.__r.__words[__i]; + } + } + return *this; + } + + union { + __long __l; + __short __s; + __raw __r; + }; + }; +#endif // _LIBCPP_CXX03_LANG __compressed_pair<__rep, allocator_type> __r_; diff --git a/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp b/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/alignof.compile.pass.cpp @@ -44,6 +44,9 @@ template using min_string = std::basic_string, min_allocator>; +template +using fancy_string = std::basic_string, fancy_pointer_allocator>; + template using test_string = std::basic_string, test_allocator>; @@ -54,6 +57,7 @@ static_assert(alignof(std::string) == 8, ""); static_assert(alignof(min_string) == 8, ""); +static_assert(alignof(fancy_string) == 8, ""); static_assert(alignof(test_string) == 8, ""); static_assert(alignof(small_string) == 2, ""); @@ -61,11 +65,13 @@ # if __WCHAR_WIDTH__ == 32 static_assert(alignof(std::wstring) == 8, ""); static_assert(alignof(min_string) == 8, ""); +static_assert(alignof(fancy_string) == 8, ""); static_assert(alignof(test_string) == 8, ""); static_assert(alignof(small_string) == 4, ""); # elif __WCHAR_WIDTH__ == 16 static_assert(alignof(std::wstring) == 8, ""); static_assert(alignof(min_string) == 8, ""); +static_assert(alignof(fancy_string) == 8, ""); static_assert(alignof(test_string) == 8, ""); static_assert(alignof(small_string) == 2, ""); # else @@ -76,6 +82,7 @@ # ifndef TEST_HAS_NO_CHAR8_T static_assert(alignof(std::u8string) == 8, ""); static_assert(alignof(min_string) == 8, ""); +static_assert(alignof(fancy_string) == 8, ""); static_assert(alignof(test_string) == 8, ""); static_assert(alignof(small_string) == 2, ""); # endif @@ -85,6 +92,8 @@ static_assert(alignof(std::u32string) == 8, ""); static_assert(alignof(min_string) == 8, ""); static_assert(alignof(min_string) == 8, ""); +static_assert(alignof(fancy_string) == 8, ""); +static_assert(alignof(fancy_string) == 8, ""); static_assert(alignof(test_string) == 8, ""); static_assert(alignof(test_string) == 8, ""); static_assert(alignof(small_string) == 2, ""); @@ -95,6 +104,7 @@ static_assert(alignof(std::string) == 4, ""); static_assert(alignof(min_string) == 4, ""); +static_assert(alignof(fancy_string) == 4, ""); static_assert(alignof(test_string) == 4, ""); static_assert(alignof(small_string) == 2, ""); @@ -102,11 +112,13 @@ # if __WCHAR_WIDTH__ == 32 static_assert(alignof(std::wstring) == 4, ""); static_assert(alignof(min_string) == 4, ""); +static_assert(alignof(fancy_string) == 4, ""); static_assert(alignof(test_string) == 4, ""); static_assert(alignof(small_string) == 4, ""); # elif __WCHAR_WIDTH__ == 16 static_assert(alignof(std::wstring) == 4, ""); static_assert(alignof(min_string) == 4, ""); +static_assert(alignof(fancy_string) == 4, ""); static_assert(alignof(test_string) == 4, ""); static_assert(alignof(small_string) == 2, ""); # else @@ -117,6 +129,7 @@ # ifndef TEST_HAS_NO_CHAR8_T static_assert(alignof(std::u8string) == 4, ""); static_assert(alignof(min_string) == 4, ""); +static_assert(alignof(fancy_string) == 4, ""); static_assert(alignof(test_string) == 4, ""); static_assert(alignof(small_string) == 2, ""); # endif @@ -126,6 +139,8 @@ static_assert(alignof(std::u32string) == 4, ""); static_assert(alignof(min_string) == 4, ""); static_assert(alignof(min_string) == 4, ""); +static_assert(alignof(fancy_string) == 4, ""); +static_assert(alignof(fancy_string) == 4, ""); static_assert(alignof(test_string) == 4, ""); static_assert(alignof(test_string) == 4, ""); static_assert(alignof(small_string) == 4, ""); diff --git a/libcxx/test/libcxx/strings/basic.string/allocator_with_fancy_pointer_non_trivial_destructor.verify.cpp b/libcxx/test/libcxx/strings/basic.string/allocator_with_fancy_pointer_non_trivial_destructor.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/strings/basic.string/allocator_with_fancy_pointer_non_trivial_destructor.verify.cpp @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// + +// Test that basic_string requires its allocator to use a pointer with a trivial destructor. +#include "test_macros.h" + +#include +#include +#include +#include + +template +class NonTriviallyDestructiblePointer { +public: + // For the std::pointer_traits interface. + using pointer = T*; + using element_type = T; + using difference_type = std::ptrdiff_t; + + template + using rebind = NonTriviallyDestructiblePointer; + + // For the std::iterator_traits interface. + using value_type = typename std::remove_cv::type; + using reference = typename std::add_lvalue_reference::type; + using iterator_category = std::random_access_iterator_tag; + + _LIBCPP_CONSTEXPR_SINCE_CXX20 NonTriviallyDestructiblePointer() {} // non-trivial constructor + _LIBCPP_CONSTEXPR_SINCE_CXX20 NonTriviallyDestructiblePointer(T* p) : ptr(p) {} + + template + NonTriviallyDestructiblePointer(const NonTriviallyDestructiblePointer& other) noexcept : ptr(other.get()) {} + + operator T*() const { return ptr; } + NonTriviallyDestructiblePointer(NonTriviallyDestructiblePointer&&) = default; + NonTriviallyDestructiblePointer(const NonTriviallyDestructiblePointer&) = default; + NonTriviallyDestructiblePointer& operator=(NonTriviallyDestructiblePointer&&) = default; + NonTriviallyDestructiblePointer& operator=(const NonTriviallyDestructiblePointer&) = default; + _LIBCPP_CONSTEXPR_SINCE_CXX20 ~NonTriviallyDestructiblePointer() {} // non-trivial destructor + T& operator*() const { return *ptr; } + T* operator->() const { return ptr; } + operator bool() const { return ptr; } + T* get() const { return ptr; } + + static NonTriviallyDestructiblePointer pointer_to(reference ref) noexcept { + return NonTriviallyDestructiblePointer(&ref); + } + +private: + T* ptr; +}; + +template +class allocator : public std::allocator { + using base = std::allocator; + +public: + using pointer = NonTriviallyDestructiblePointer; + pointer allocate(size_t n) { return base::allocate(n); } + void deallocate(pointer p, size_t n) { return base::deallocate(p, n); } +}; + +int main() { +#if TEST_STD_VER >= 20 + std::basic_string, allocator > s; // expected-no-diagnostics +#else + std::basic_string, allocator > + s; // expected-error-re@* {{{{.*}}pointer type must have a trivial destructor.}} +#endif +} diff --git a/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp b/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/sizeof.compile.pass.cpp @@ -42,6 +42,9 @@ template using min_string = std::basic_string, min_allocator >; +template +using fancy_string = std::basic_string, fancy_pointer_allocator >; + template using test_string = std::basic_string, test_allocator >; @@ -52,6 +55,7 @@ static_assert(sizeof(std::string) == 24, ""); static_assert(sizeof(min_string) == 24, ""); +static_assert(sizeof(fancy_string) == 24, ""); static_assert(sizeof(test_string) == 32, ""); static_assert(sizeof(small_string) == 6, ""); @@ -59,11 +63,13 @@ # if __WCHAR_WIDTH__ == 32 static_assert(sizeof(std::wstring) == 24, ""); static_assert(sizeof(min_string) == 24, ""); +static_assert(sizeof(fancy_string) == 24, ""); static_assert(sizeof(test_string) == 32, ""); static_assert(sizeof(small_string) == 12, ""); # elif __WCHAR_WIDTH__ == 16 static_assert(sizeof(std::wstring) == 24, ""); static_assert(sizeof(min_string) == 24, ""); +static_assert(sizeof(fancy_string) == 24, ""); static_assert(sizeof(test_string) == 32, ""); static_assert(sizeof(small_string) == 6, ""); # else @@ -74,6 +80,7 @@ # ifndef TEST_HAS_NO_CHAR8_T static_assert(sizeof(std::u8string) == 24, ""); static_assert(sizeof(min_string) == 24, ""); +static_assert(sizeof(fancy_string) == 24, ""); static_assert(sizeof(test_string) == 32, ""); static_assert(sizeof(small_string) == 6, ""); # endif @@ -82,7 +89,9 @@ static_assert(sizeof(std::u16string) == 24, ""); static_assert(sizeof(std::u32string) == 24, ""); static_assert(sizeof(min_string) == 24, ""); +static_assert(sizeof(fancy_string) == 24, ""); static_assert(sizeof(min_string) == 24, ""); +static_assert(sizeof(fancy_string) == 24, ""); static_assert(sizeof(test_string) == 32, ""); static_assert(sizeof(test_string) == 32, ""); static_assert(sizeof(small_string) == 6, ""); @@ -93,6 +102,7 @@ static_assert(sizeof(std::string) == 12, ""); static_assert(sizeof(min_string) == 12, ""); +static_assert(sizeof(fancy_string) == 12, ""); static_assert(sizeof(test_string) == 24, ""); static_assert(sizeof(small_string) == 6, ""); @@ -100,11 +110,13 @@ # if __WCHAR_WIDTH__ == 32 static_assert(sizeof(std::wstring) == 12, ""); static_assert(sizeof(min_string) == 12, ""); +static_assert(sizeof(fancy_string) == 12, ""); static_assert(sizeof(test_string) == 24, ""); static_assert(sizeof(small_string) == 12, ""); # elif __WCHAR_WIDTH__ == 16 static_assert(sizeof(std::wstring) == 12, ""); static_assert(sizeof(min_string) == 12, ""); +static_assert(sizeof(fancy_string) == 12, ""); static_assert(sizeof(test_string) == 24, ""); static_assert(sizeof(small_string) == 6, ""); # else @@ -115,6 +127,7 @@ # ifndef TEST_HAS_NO_CHAR8_T static_assert(sizeof(std::u8string) == 12, ""); static_assert(sizeof(min_string) == 12, ""); +static_assert(sizeof(fancy_string) == 12, ""); static_assert(sizeof(test_string) == 24, ""); static_assert(sizeof(small_string) == 6, ""); # endif @@ -124,6 +137,8 @@ static_assert(sizeof(std::u32string) == 12, ""); static_assert(sizeof(min_string) == 12, ""); static_assert(sizeof(min_string) == 12, ""); +static_assert(sizeof(fancy_string) == 12, ""); +static_assert(sizeof(fancy_string) == 12, ""); static_assert(sizeof(test_string) == 24, ""); static_assert(sizeof(test_string) == 24, ""); static_assert(sizeof(small_string) == 6, ""); diff --git a/libcxx/test/libcxx/strings/basic.string/string.access/assert.back.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.access/assert.back.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.access/assert.back.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.access/assert.back.pass.cpp @@ -19,18 +19,18 @@ #include "check_assertion.h" #include "min_allocator.h" +#include "test_allocator.h" + +template +void test() { + S s; + TEST_LIBCPP_ASSERT_FAILURE(s.back(), "string::back(): string is empty"); +} int main(int, char**) { - { - std::string s; - TEST_LIBCPP_ASSERT_FAILURE(s.back(), "string::back(): string is empty"); - } - - { - typedef std::basic_string, min_allocator > S; - S s; - TEST_LIBCPP_ASSERT_FAILURE(s.back(), "string::back(): string is empty"); - } - - return 0; + test(); + test, min_allocator > >(); + test, fancy_pointer_allocator >>(); + + return 0; } diff --git a/libcxx/test/libcxx/strings/basic.string/string.access/assert.cback.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.access/assert.cback.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.access/assert.cback.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.access/assert.cback.pass.cpp @@ -19,18 +19,18 @@ #include "check_assertion.h" #include "min_allocator.h" +#include "test_allocator.h" + +template +void test() { + const S s; + TEST_LIBCPP_ASSERT_FAILURE(s.back(), "string::back(): string is empty"); +} int main(int, char**) { - { - std::string const s; - TEST_LIBCPP_ASSERT_FAILURE(s.back(), "string::back(): string is empty"); - } - - { - typedef std::basic_string, min_allocator > S; - const S s; - TEST_LIBCPP_ASSERT_FAILURE(s.back(), "string::back(): string is empty"); - } - - return 0; + test(); + test, min_allocator > >(); + test, fancy_pointer_allocator >>(); + + return 0; } diff --git a/libcxx/test/libcxx/strings/basic.string/string.access/assert.cfront.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.access/assert.cfront.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.access/assert.cfront.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.access/assert.cfront.pass.cpp @@ -19,19 +19,18 @@ #include "check_assertion.h" #include "min_allocator.h" +#include "test_allocator.h" + +template +void test() { + const S s; + TEST_LIBCPP_ASSERT_FAILURE(s.front(), "string::front(): string is empty"); +} int main(int, char**) { - { - typedef std::string S; - const S s; - TEST_LIBCPP_ASSERT_FAILURE(s.front(), "string::front(): string is empty"); - } - - { - typedef std::basic_string, min_allocator > S; - const S s; - TEST_LIBCPP_ASSERT_FAILURE(s.front(), "string::front(): string is empty"); - } + test(); + test, min_allocator > >(); + test, fancy_pointer_allocator >>(); return 0; } diff --git a/libcxx/test/libcxx/strings/basic.string/string.access/assert.cindex.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.access/assert.cindex.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.access/assert.cindex.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.access/assert.cindex.pass.cpp @@ -20,21 +20,19 @@ #include "check_assertion.h" #include "min_allocator.h" +#include "test_allocator.h" + +template +void test() { + const S s; + assert(s[0] == 0); + TEST_LIBCPP_ASSERT_FAILURE(s[1], "string index out of bounds"); +} int main(int, char**) { - { - typedef std::basic_string, min_allocator > S; - const S s; - assert(s[0] == 0); - TEST_LIBCPP_ASSERT_FAILURE(s[1], "string index out of bounds"); - } - - { - typedef std::string S; - const S s; - assert(s[0] == 0); - TEST_LIBCPP_ASSERT_FAILURE(s[1], "string index out of bounds"); - } + test(); + test, min_allocator > >(); + test, fancy_pointer_allocator >>(); return 0; } diff --git a/libcxx/test/libcxx/strings/basic.string/string.access/assert.front.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.access/assert.front.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.access/assert.front.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.access/assert.front.pass.cpp @@ -20,19 +20,18 @@ #include "check_assertion.h" #include "min_allocator.h" +#include "test_allocator.h" + +template +void test() { + S s; + TEST_LIBCPP_ASSERT_FAILURE(s.front(), "string::front(): string is empty"); +} int main(int, char**) { - { - typedef std::string S; - S s; - TEST_LIBCPP_ASSERT_FAILURE(s.front(), "string::front(): string is empty"); - } - - { - typedef std::basic_string, min_allocator > S; - S s; - TEST_LIBCPP_ASSERT_FAILURE(s.front(), "string::front(): string is empty"); - } + test(); + test, min_allocator > >(); + test, fancy_pointer_allocator >>(); return 0; } diff --git a/libcxx/test/libcxx/strings/basic.string/string.access/assert.index.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.access/assert.index.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.access/assert.index.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.access/assert.index.pass.cpp @@ -20,21 +20,19 @@ #include "check_assertion.h" #include "min_allocator.h" +#include "test_allocator.h" + +template +void test() { + S s; + assert(s[0] == 0); + TEST_LIBCPP_ASSERT_FAILURE(s[1], "string index out of bounds"); +} int main(int, char**) { - { - typedef std::string S; - S s; - assert(s[0] == 0); - TEST_LIBCPP_ASSERT_FAILURE(s[1], "string index out of bounds"); - } - - { - typedef std::basic_string, min_allocator > S; - S s; - assert(s[0] == 0); - TEST_LIBCPP_ASSERT_FAILURE(s[1], "string index out of bounds"); - } + test(); + test, min_allocator > >(); + test, fancy_pointer_allocator >>(); return 0; } diff --git a/libcxx/test/libcxx/strings/basic.string/string.capacity/PR53170.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.capacity/PR53170.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.capacity/PR53170.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.capacity/PR53170.pass.cpp @@ -31,6 +31,7 @@ #include "test_macros.h" #include "min_allocator.h" +#include "test_allocator.h" template void test() { @@ -73,6 +74,7 @@ #if TEST_STD_VER >= 11 test, min_allocator > >(); + test, fancy_pointer_allocator > >(); #endif return 0; diff --git a/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp --- a/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp +++ b/libcxx/test/libcxx/strings/basic.string/string.capacity/max_size.pass.cpp @@ -16,81 +16,93 @@ #include #include "test_macros.h" +#include "test_allocator.h" // alignment of the string heap buffer is hardcoded to 16 static const size_t alignment = 16; +template