From 5e3a1ea3d89d62972e1f036b2ede37a80b880bdf Mon Sep 17 00:00:00 2001 From: Jonathan Wakely Date: Tue, 11 May 2021 15:01:01 +0100 Subject: [PATCH] libstdc++: Fix missing members in std::allocator The changes in 75c6a925dab5b7af9ab47c10906cb0e140261cc2 were slightly incorrect, because the converting constructor should be noexcept, and the POCMA and is_always_equal traits should still be present in C++20. This fixes it, and slightly refactors the preprocessor conditions and order of members. Also add comments explaining things. The non-standard construct and destroy members added for PR 78052 can be private if allocator_traits> is made a friend. libstdc++-v3/ChangeLog: * include/bits/allocator.h (allocator) [C++20]: Add missing noexcept to constructor. Restore missing POCMA and is_always_equal_traits. [C++17]: Make construct and destroy members private and declare allocator_traits as a friend. * include/bits/memoryfwd.h (allocator_traits): Declare. * include/ext/malloc_allocator.h (malloc_allocator::allocate): Add nodiscard attribute. Add static assertion for LWG 3307. * include/ext/new_allocator.h (new_allocator::allocate): Add static assertion for LWG 3307. * testsuite/20_util/allocator/void.cc: Check that converting constructor is noexcept. Check for propagation traits and size_type and difference_type. Check that pointer and const_pointer are gone in C++20. --- libstdc++-v3/include/bits/allocator.h | 44 ++++++++++++++++++------ libstdc++-v3/include/bits/memoryfwd.h | 7 ++-- libstdc++-v3/include/ext/malloc_allocator.h | 8 ++++- libstdc++-v3/include/ext/new_allocator.h | 8 ++++- libstdc++-v3/testsuite/20_util/allocator/void.cc | 40 ++++++++++++++++++--- 5 files changed, 88 insertions(+), 19 deletions(-) diff --git a/libstdc++-v3/include/bits/allocator.h b/libstdc++-v3/include/bits/allocator.h index c5c1f28..73d5d7a 100644 --- a/libstdc++-v3/include/bits/allocator.h +++ b/libstdc++-v3/include/bits/allocator.h @@ -60,6 +60,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * @{ */ + // Since C++20 the primary template should be used for allocator, + // but then it would have a non-trivial default ctor and dtor, which + // would be an ABI change. So C++20 still uses the allocator explicit + // specialization, with the historical ABI properties, but with the same + // members that are present in the primary template. + +#if ! _GLIBCXX_INLINE_VERSION /// allocator specialization. template<> class allocator @@ -68,28 +75,40 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION typedef void value_type; typedef size_t size_type; typedef ptrdiff_t difference_type; + #if __cplusplus <= 201703L + // These were removed for C++20. typedef void* pointer; typedef const void* const_pointer; template struct rebind { typedef allocator<_Tp1> other; }; -#else - allocator() = default; - - template - constexpr - allocator(const allocator<_Up>&) { } -#endif // ! C++20 +#endif -#if __cplusplus >= 201103L && __cplusplus <= 201703L +#if __cplusplus >= 201103L // _GLIBCXX_RESOLVE_LIB_DEFECTS // 2103. std::allocator propagate_on_container_move_assignment typedef true_type propagate_on_container_move_assignment; typedef true_type is_always_equal; +#if __cplusplus >= 202002L + allocator() = default; + + template + constexpr + allocator(const allocator<_Up>&) noexcept { } + + // No allocate member because it's ill-formed by LWG 3307. + // No deallocate member because it would be undefined to call it + // with any pointer which wasn't obtained from allocate. + +#else // ! C++20 + private: + // This uses construct and destroy in C++11/14/17 modes. + friend allocator_traits>; + template void construct(_Up* __p, _Args&&... __args) @@ -101,11 +120,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION destroy(_Up* __p) noexcept(std::is_nothrow_destructible<_Up>::value) { __p->~_Up(); } -#endif // C++11 to C++17 +#endif // C++17 +#endif // C++11 + }; +#endif // ! _GLIBCXX_INLINE_VERSION /** - * @brief The @a standard allocator, as per [20.4]. + * @brief The @a standard allocator, as per C++03 [20.4.1]. * * See https://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html#std.util.memory.allocator * for further details. @@ -119,7 +141,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION typedef _Tp value_type; typedef size_t size_type; typedef ptrdiff_t difference_type; + #if __cplusplus <= 201703L + // These were removed for C++20. typedef _Tp* pointer; typedef const _Tp* const_pointer; typedef _Tp& reference; diff --git a/libstdc++-v3/include/bits/memoryfwd.h b/libstdc++-v3/include/bits/memoryfwd.h index 10a386c..b0f0307 100644 --- a/libstdc++-v3/include/bits/memoryfwd.h +++ b/libstdc++-v3/include/bits/memoryfwd.h @@ -63,15 +63,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template class allocator; -#if __cplusplus <= 201703L template<> class allocator; -#endif #if __cplusplus >= 201103L - /// Declare uses_allocator so it can be specialized in \ etc. + /// Declare uses_allocator so it can be specialized in `` etc. template struct uses_allocator; + + template + struct allocator_traits; #endif /// @} group memory diff --git a/libstdc++-v3/include/ext/malloc_allocator.h b/libstdc++-v3/include/ext/malloc_allocator.h index 3112f7f..1e90b17 100644 --- a/libstdc++-v3/include/ext/malloc_allocator.h +++ b/libstdc++-v3/include/ext/malloc_allocator.h @@ -99,9 +99,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // NB: __n is permitted to be 0. The C++ standard says nothing // about what the return value is when __n == 0. - _Tp* + _GLIBCXX_NODISCARD _Tp* allocate(size_type __n, const void* = 0) { +#if __cplusplus >= 201103L + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 3308. std::allocator().allocate(n) + static_assert(sizeof(_Tp) != 0, "cannot allocate incomplete types"); +#endif + if (__builtin_expect(__n > this->_M_max_size(), false)) { // _GLIBCXX_RESOLVE_LIB_DEFECTS diff --git a/libstdc++-v3/include/ext/new_allocator.h b/libstdc++-v3/include/ext/new_allocator.h index 9e624ba..3fb893b 100644 --- a/libstdc++-v3/include/ext/new_allocator.h +++ b/libstdc++-v3/include/ext/new_allocator.h @@ -42,7 +42,7 @@ namespace __gnu_cxx _GLIBCXX_VISIBILITY(default) _GLIBCXX_BEGIN_NAMESPACE_VERSION /** - * @brief An allocator that uses global new, as per [20.4]. + * @brief An allocator that uses global new, as per C++03 [20.4.1]. * @ingroup allocators * * This is precisely the allocator defined in the C++ Standard. @@ -102,6 +102,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _GLIBCXX_NODISCARD _Tp* allocate(size_type __n, const void* = static_cast(0)) { +#if __cplusplus >= 201103L + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 3308. std::allocator().allocate(n) + static_assert(sizeof(_Tp) != 0, "cannot allocate incomplete types"); +#endif + if (__builtin_expect(__n > this->_M_max_size(), false)) { // _GLIBCXX_RESOLVE_LIB_DEFECTS diff --git a/libstdc++-v3/testsuite/20_util/allocator/void.cc b/libstdc++-v3/testsuite/20_util/allocator/void.cc index a172606..e3d024d 100644 --- a/libstdc++-v3/testsuite/20_util/allocator/void.cc +++ b/libstdc++-v3/testsuite/20_util/allocator/void.cc @@ -33,6 +33,18 @@ test01() std::allocator_traits::destroy(a, &i); } +static_assert( std::allocator::propagate_on_container_move_assignment(), + "POCMA trait should always be present" ); +static_assert( std::allocator::is_always_equal(), + "is_always_equal trait should always be present" ); + +static_assert( + std::is_same::size_type, std::size_t>(), + "size_type is size_t" ); +static_assert( + std::is_same::difference_type, std::ptrdiff_t>(), + "size_type is size_t" ); + // These properties are formally unspecified, but have always been true for // the libstdc++ definition of allocator. static_assert( @@ -48,12 +60,32 @@ static_assert( std::is_trivially_destructible>::value, "explicit specialization has trivial destructor"); -#if __cplusplus > 201703L +#if __cplusplus >= 202002L // C++20 removes the allocator explicit specialization, so it can now be // constructed using the converting constructor from other specializations. -static_assert( std::is_constructible_v, - std::allocator> ); -#endif +static_assert( std::is_nothrow_constructible_v, + std::allocator> ); + +template +concept has_pointer = requires { typename T::pointer; }; +template +concept has_const_pointer = requires { typename T::const_pointer; }; +template +concept has_size_type = requires { typename T::size_type; }; +template +concept has_difference_type = requires { typename T::difference_type; }; + +// These were removed for C++20 +static_assert( ! has_pointer> ); +static_assert( ! has_const_pointer> ); + +#else +static_assert( + std::is_same::pointer, void*>(), + "pointer is void*" ); +static_assert( std::is_same::const_pointer, const void*>(), + "const_pointer is const void*" ); +#endif // C++20 int main() -- 2.7.4