https://gcc.gnu.org/g:f29d1b5836790ec795cb51bcfe25f7270b3e9f30
commit r15-5909-gf29d1b5836790ec795cb51bcfe25f7270b3e9f30 Author: Jonathan Wakely <jwak...@redhat.com> Date: Fri Nov 15 19:06:47 2024 +0000 libstdc++: Add fancy pointer support to std::list [PR57272] Currently std::list uses raw pointers to connect its nodes, which is non-conforming. We should use the allocator's pointer type everywhere that a "pointer" is needed. Because the existing types like _List_node<T> are part of the ABI now, we can't change them. To support nodes that are connected by fancy pointers we need a parallel hierarchy of node types. This change introduces new class templates parameterized on the allocator's void_pointer type, __list::_Node_base and __list::_Node_header, and new class templates parameterized on the allocator's pointer type, __list::Node, __list::_Iterator. The iterator class template is used for both iterator and const_iterator. Whether std::list<T, A> should use the old _List_node<T> or new _list::_Node<A::pointer> type family internally is controlled by a new __list::_Node_traits traits template. Because std::pointer_traits and std::__to_address are not defined for C++98, there is no way to support fancy pointers in C++98. For C++98 the _Node_traits traits always choose the old _List_node family. In case anybody is currently using std::list with an allocator that has a fancy pointer, this change would be an ABI break, because their std::list instantiations would start to (correctly) use the fancy pointer type. If the fancy pointer just contains a single pointer and so has the same size, layout, and object represenation as a raw pointer, the code might still work (despite being an ODR violation). But if their fancy pointer has a different representation, they would need to recompile all their code using that allocator with std::list. Because std::list will never use fancy pointers in C++98 mode, recompiling everything to use fancy pointers isn't even possible if mixing C++98 and C++11 code that uses std::list. To alleviate this problem, compiling with -D_GLIBCXX_USE_ALLOC_PTR_FOR_LIST=0 will force std::list to have the old, non-conforming behaviour and use raw pointers internally. For testing purposes, compiling with -D_GLIBCXX_USE_ALLOC_PTR_FOR_LIST=9001 will force std::list to always use the new node types. This macro is currently undocumented, which needs to be fixed. The original _List_node<T> type is trivially constructible and trivially destructible, but the new __list::_Node<Ptr> type might not be, depending on the fancy pointer data members in _Node_base. This means that std::list needs to explicitly construct and destroy the node object, not just the value that it contains. This commit adds a new __allocated_obj helper which wraps an __allocated_ptr and additionally constructs and destroys an object in the allocated storage. Pretty printers for std::list need to be updated to handle the new node types. Potentially we just can't pretty print them, because we don't know how to follow the fancy pointers to traverse the list. libstdc++-v3/ChangeLog: PR libstdc++/57272 PR libstdc++/110952 * include/bits/allocated_ptr.h (__allocated_ptr::get): Add const. (__allocated_ptr::operator bool, __allocated_ptr::release): New member functions. (__allocate_guarded): Add inline. (__allocated_obj): New class template. (__allocate_guarded_obj): New function template. * include/bits/list.tcc (_List_base::_M_clear()): Replace uses of raw pointers. Use _M_destroy_node. (list::emplace, list::insert): Likewise. (list::sort): Adjust check for 0 or 1 wsize. Use template argument list for _Scratch_list. * include/bits/stl_list.h (_GLIBCXX_USE_ALLOC_PTR_FOR_LIST): Define. (_List_node_base::_Base_ptr): New typedef. (_List_node_base::_M_base): New member functions. (_List_node_header::_M_base): Make public and add using-declaration for base class overload. (__list::_Node_traits, __list::_Node_base) (__list::_Node_header, __list::_Node, __list::_Iterator): New class templates. (_Scratch_list): Turn class into class template. Use _Base_ptr typedef instead of _List_node_base*. (_List_node::_Node_ptr): New typedef. (_List_node::_M_node_ptr): New member function. (_List_base, _List_impl): Use _Node_traits to get node types. (_List_base::_M_put_node): Convert to fancy pointer if needed. (_List_base::_M_destroy_node): New member function. (_List_base(_List_base&&, _Node_alloc_type&&)): Use if constexpr to make function a no-op for fancy pointers. (_List_base::_S_distance, _List_base::_M_distance) (_List_base::_M_node_count): Likewise. (list): Use _Node_traits to get iterator, node and pointer types. (list::_M_create_node): Use _Node_ptr typedef instead of _Node*. Use __allocate_guarded_obj instead of _M_get_node. (list::end, list::cend, list::empty): Use node header's _M_base() function instead of taking its address. (list::swap): Use _Node_traits to get node base type. (list::_M_create_node, list::_M_insert): Use _Node_ptr instead of _Node*. (list::_M_erase): Likewise. Use _M_destroy_node. (__distance): Overload for __list::_Iterator. (_Node_base::swap, _Node_base::_M_transfer): Define non-inline member functions of class templates. (_Node_header::_M_reverse): Likewise. * testsuite/23_containers/list/capacity/29134.cc: Check max_size for allocator of new node type. * testsuite/23_containers/list/capacity/node_sizes.cc: New test. * testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr.cc: New test. * testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr_ignored.cc: New test. Diff: --- libstdc++-v3/include/bits/allocated_ptr.h | 44 +- libstdc++-v3/include/bits/list.tcc | 52 +- libstdc++-v3/include/bits/stl_list.h | 590 ++++++++++++++++++--- .../testsuite/23_containers/list/capacity/29134.cc | 5 + .../23_containers/list/capacity/node_sizes.cc | 24 + .../list/requirements/completeness.cc | 19 + .../explicit_instantiation/alloc_ptr.cc | 87 +++ .../explicit_instantiation/alloc_ptr_ignored.cc | 4 + 8 files changed, 737 insertions(+), 88 deletions(-) diff --git a/libstdc++-v3/include/bits/allocated_ptr.h b/libstdc++-v3/include/bits/allocated_ptr.h index 26a07a1d34f2..3d0f62bfa9fa 100644 --- a/libstdc++-v3/include/bits/allocated_ptr.h +++ b/libstdc++-v3/include/bits/allocated_ptr.h @@ -82,22 +82,60 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return *this; } + explicit operator bool() const noexcept { return (bool)_M_ptr; } + /// Get the address that the owned pointer refers to. - value_type* get() { return std::__to_address(_M_ptr); } + value_type* get() const { return std::__to_address(_M_ptr); } + + pointer release() { return std::__exchange(_M_ptr, nullptr); } private: _Alloc* _M_alloc; pointer _M_ptr; }; - /// Allocate space for a single object using __a + /// Allocate space for a single object using __a. template<typename _Alloc> - __allocated_ptr<_Alloc> + inline __allocated_ptr<_Alloc> __allocate_guarded(_Alloc& __a) { return { __a, std::allocator_traits<_Alloc>::allocate(__a, 1) }; } + /// RAII type for constructing/destroying an object with an allocated pointer + template<typename _Alloc> + struct __allocated_obj : __allocated_ptr<_Alloc> + { + using value_type = typename __allocated_ptr<_Alloc>::value_type; + + __allocated_obj(__allocated_obj<_Alloc>&&) = default; + + // Default-initialize a value_type at *__ptr + __allocated_obj(__allocated_ptr<_Alloc>&& __ptr) + : __allocated_ptr<_Alloc>(std::move(__ptr)) + { ::new ((void*)this->get()) value_type; } + + // Call the destructor if an object is owned. + ~__allocated_obj() + { + if (static_cast<bool>(*this)) + this->get()->~value_type(); + } + + using __allocated_ptr<_Alloc>::operator=; + + value_type& operator*() const { return *this->get(); } + value_type* operator->() const { return this->get(); } + }; + + /// Construct an object in storage allocated using __a. + template<typename _Alloc> + inline __allocated_obj<_Alloc> + __allocate_guarded_obj(_Alloc& __a) + { + return { std::__allocate_guarded(__a) }; + } + /// @endcond _GLIBCXX_END_NAMESPACE_VERSION } // namespace std diff --git a/libstdc++-v3/include/bits/list.tcc b/libstdc++-v3/include/bits/list.tcc index 65835c1379f4..2102e15eec23 100644 --- a/libstdc++-v3/include/bits/list.tcc +++ b/libstdc++-v3/include/bits/list.tcc @@ -66,19 +66,14 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER _List_base<_Tp, _Alloc>:: _M_clear() _GLIBCXX_NOEXCEPT { - typedef _List_node<_Tp> _Node; - __detail::_List_node_base* __cur = _M_impl._M_node._M_next; - while (__cur != &_M_impl._M_node) + typedef typename _Node_traits::_Node _Node; + typedef typename _Node_traits::_Node_base _Node_base; + typename _Node_base::_Base_ptr __cur = _M_impl._M_node._M_next; + while (__cur != _M_impl._M_node._M_base()) { - _Node* __tmp = static_cast<_Node*>(__cur); - __cur = __tmp->_M_next; - _Tp* __val = __tmp->_M_valptr(); -#if __cplusplus >= 201103L - _Node_alloc_traits::destroy(_M_get_Node_allocator(), __val); -#else - _Tp_alloc_type(_M_get_Node_allocator()).destroy(__val); -#endif - _M_put_node(__tmp); + _Node& __tmp = static_cast<_Node&>(*__cur); + __cur = __tmp._M_next; + this->_M_destroy_node(__tmp._M_node_ptr()); } } @@ -89,10 +84,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER list<_Tp, _Alloc>:: emplace(const_iterator __position, _Args&&... __args) { - _Node* __tmp = _M_create_node(std::forward<_Args>(__args)...); + _Node_ptr __tmp = _M_create_node(std::forward<_Args>(__args)...); __tmp->_M_hook(__position._M_const_cast()._M_node); this->_M_inc_size(1); - return iterator(__tmp); + return iterator(__tmp->_M_base()); } #endif @@ -105,10 +100,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER insert(iterator __position, const value_type& __x) #endif { - _Node* __tmp = _M_create_node(__x); + _Node_ptr __tmp = _M_create_node(__x); __tmp->_M_hook(__position._M_const_cast()._M_node); this->_M_inc_size(1); - return iterator(__tmp); + return iterator(__tmp->_M_base()); } #if __cplusplus >= 201103L @@ -482,10 +477,13 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER sort() { // Do nothing if the list has length 0 or 1. - if (this->_M_impl._M_node._M_next != &this->_M_impl._M_node - && this->_M_impl._M_node._M_next->_M_next != &this->_M_impl._M_node) + if (empty() || ++begin() == end()) + return; + { - using __detail::_Scratch_list; + typedef __list::_Scratch_list<typename _Node_traits::_Node_base> + _Scratch_list; + // The algorithm used here is largely unchanged from the SGI STL // and is described in The C++ Standard Template Library by Plauger, // Stepanov, Lee, Musser. @@ -499,7 +497,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER _Scratch_list* __fill = __tmp; _Scratch_list* __counter; - _Scratch_list::_Ptr_cmp<iterator, void> __ptr_comp; + typename _Scratch_list::template _Ptr_cmp<iterator, void> __ptr_comp; __try { @@ -614,17 +612,21 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER sort(_StrictWeakOrdering __comp) { // Do nothing if the list has length 0 or 1. - if (this->_M_impl._M_node._M_next != &this->_M_impl._M_node - && this->_M_impl._M_node._M_next->_M_next != &this->_M_impl._M_node) + if (empty() || ++begin() == end()) + return; + { - using __detail::_Scratch_list; + typedef __list::_Scratch_list<typename _Node_traits::_Node_base> + _Scratch_list; + _Scratch_list __carry; _Scratch_list __tmp[64]; _Scratch_list* __fill = __tmp; _Scratch_list* __counter; - _Scratch_list::_Ptr_cmp<iterator, _StrictWeakOrdering> __ptr_comp - = { __comp }; + typename _Scratch_list:: + template _Ptr_cmp<iterator, _StrictWeakOrdering> __ptr_comp + = { __comp }; __try { diff --git a/libstdc++-v3/include/bits/stl_list.h b/libstdc++-v3/include/bits/stl_list.h index db6b31fdc166..9f7d27242b60 100644 --- a/libstdc++-v3/include/bits/stl_list.h +++ b/libstdc++-v3/include/bits/stl_list.h @@ -63,6 +63,7 @@ #if __cplusplus >= 201103L #include <initializer_list> #include <bits/allocated_ptr.h> +#include <bits/ptr_traits.h> #include <ext/aligned_buffer.h> #endif #if __glibcxx_ranges_to_container // C++ >= 23 @@ -70,6 +71,13 @@ # include <bits/ranges_util.h> // ranges::subrange #endif +#if __cplusplus < 201103L +# undef _GLIBCXX_USE_ALLOC_PTR_FOR_LIST +# define _GLIBCXX_USE_ALLOC_PTR_FOR_LIST 0 +#elif ! defined _GLIBCXX_USE_ALLOC_PTR_FOR_LIST +# define _GLIBCXX_USE_ALLOC_PTR_FOR_LIST 1 +#endif + namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION @@ -85,6 +93,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION /// Common part of a node in the %list. struct _List_node_base { + typedef _List_node_base* _Base_ptr; + _List_node_base* _M_next; _List_node_base* _M_prev; @@ -103,6 +113,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION void _M_unhook() _GLIBCXX_USE_NOEXCEPT; + + _List_node_base* _M_base() { return this; } + const _List_node_base* _M_base() const { return this; } }; struct _List_size @@ -113,7 +126,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif }; - /// The %list node header. struct _List_node_header : public _List_node_base, _List_size { @@ -158,18 +170,313 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _List_size::operator=(_List_size()); } + using _List_node_base::_M_base; +#if ! _GLIBCXX_INLINE_VERSION + _List_node_base* _M_base() { return this; } // XXX GLIBCXX_ABI Deprecated +#endif + }; + + } // namespace detail + +#if _GLIBCXX_USE_ALLOC_PTR_FOR_LIST +_GLIBCXX_BEGIN_NAMESPACE_CONTAINER +_GLIBCXX_BEGIN_NAMESPACE_CXX11 + template<typename _Tp, typename _Allocator> class list; +_GLIBCXX_END_NAMESPACE_CXX11 +_GLIBCXX_END_NAMESPACE_CONTAINER + +namespace __list +{ + // The base class for a list node. Contains the pointers connecting nodes. + template<typename _VoidPtr> + struct _Node_base + { + using _Base_ptr = __ptr_rebind<_VoidPtr, _Node_base>; + _Base_ptr _M_next; + _Base_ptr _M_prev; + + static void + swap(_Node_base& __x, _Node_base& __y) noexcept; + + void + _M_transfer(_Base_ptr const __first, _Base_ptr const __last); + + void + _M_hook(_Base_ptr const __position) noexcept + { + auto __self = pointer_traits<_Base_ptr>::pointer_to(*this); + this->_M_next = __position; + this->_M_prev = __position->_M_prev; + __position->_M_prev->_M_next = __self; + __position->_M_prev = __self; + } + + void + _M_unhook() noexcept + { + auto const __next_node = this->_M_next; + auto const __prev_node = this->_M_prev; + __prev_node->_M_next = __next_node; + __next_node->_M_prev = __prev_node; + } + + // This is not const-correct, but it's only used in a const access path + // by std::list::empty(), where it doesn't escape, and by + // std::list::end() const, where the pointer is used to initialize a + // const_iterator and so constness is restored. + _Base_ptr + _M_base() const noexcept + { + return pointer_traits<_Base_ptr>:: + pointer_to(const_cast<_Node_base&>(*this)); + } + }; + + using ::std::__detail::_List_size; + + // The special sentinel node contained by a std::list. + // begin()->_M_node->_M_prev and end()->_M_node point to this header. + // This is not a complete node, as it doesn't contain a value. + template<typename _VoidPtr> + struct _Node_header + : public _Node_base<_VoidPtr>, _List_size + { + _Node_header() noexcept + { _M_init(); } + + _Node_header(_Node_header&& __x) noexcept + : _Node_base<_VoidPtr>(__x), _List_size(__x) + { + if (__x._M_base()->_M_next == __x._M_base()) + this->_M_next = this->_M_prev = this->_M_base(); + else + { + this->_M_next->_M_prev = this->_M_prev->_M_next = this->_M_base(); + __x._M_init(); + } + } + + void + _M_move_nodes(_Node_header&& __x) noexcept + { + auto const __xnode = __x._M_base(); + if (__xnode->_M_next == __xnode) + _M_init(); + else + { + auto const __node = this->_M_base(); + __node->_M_next = __xnode->_M_next; + __node->_M_prev = __xnode->_M_prev; + __node->_M_next->_M_prev = __node->_M_prev->_M_next = __node; + _List_size::operator=(__x); + __x._M_init(); + } + } + + void + _M_init() noexcept + { + this->_M_next = this->_M_prev = this->_M_base(); + _List_size::operator=(_List_size()); + } + + void _M_reverse() noexcept; + }; + + // The node type used for allocators with fancy pointers. + template<typename _ValPtr> + struct _Node : public __list::_Node_base<__ptr_rebind<_ValPtr, void>> + { + using value_type = typename pointer_traits<_ValPtr>::element_type; + using _Node_ptr = __ptr_rebind<_ValPtr, _Node>; + + union { + value_type _M_data; + }; + + _Node() { } + ~_Node() { } + _Node(_Node&&) = delete; + + value_type* _M_valptr() { return std::__addressof(_M_data); } + value_type const* _M_valptr() const { return std::__addressof(_M_data); } + + _Node_ptr + _M_node_ptr() + { return pointer_traits<_Node_ptr>::pointer_to(*this); } + }; + + template<bool _Const, typename _Ptr> class _Iterator; + + template<bool _Const, typename _Ptr> + class _Iterator + { + using _Node = __list::_Node<_Ptr>; + using _Base_ptr + = typename __list::_Node_base<__ptr_rebind<_Ptr, void>>::_Base_ptr; + + template<typename _Tp> + using __maybe_const = __conditional_t<_Const, const _Tp, _Tp>; + + public: + using value_type = typename pointer_traits<_Ptr>::element_type; + using difference_type = ptrdiff_t; + using iterator_category = bidirectional_iterator_tag; + using pointer = __maybe_const<value_type>*; + using reference = __maybe_const<value_type>&; + + constexpr _Iterator() noexcept : _M_node() { } + + _Iterator(const _Iterator&) = default; + _Iterator& operator=(const _Iterator&) = default; + +#ifdef __glibcxx_concepts + constexpr + _Iterator(const _Iterator<false, _Ptr>& __i) requires _Const +#else + template<bool _OtherConst, + typename = __enable_if_t<_Const && !_OtherConst>> + constexpr + _Iterator(const _Iterator<_OtherConst, _Ptr>& __i) +#endif + : _M_node(__i._M_node) { } + + constexpr explicit + _Iterator(_Base_ptr __x) noexcept + : _M_node(__x) { } + + // Must downcast from _Node_base to _Node to get to value. + [[__nodiscard__]] + constexpr reference + operator*() const noexcept + { return static_cast<_Node&>(*_M_node)._M_data; } + + [[__nodiscard__]] + constexpr pointer + operator->() const noexcept + { return std::__addressof(operator*()); } + + _GLIBCXX14_CONSTEXPR _Iterator& + operator++() noexcept + { + _M_node = _M_node->_M_next; + return *this; + } + + _GLIBCXX14_CONSTEXPR _Iterator + operator++(int) noexcept + { + auto __tmp = *this; + _M_node = _M_node->_M_next; + return __tmp; + } + + _GLIBCXX14_CONSTEXPR _Iterator& + operator--() noexcept + { + _M_node = _M_node->_M_prev; + return *this; + } + + _GLIBCXX14_CONSTEXPR _Iterator + operator--(int) noexcept + { + auto __tmp = *this; + _M_node = _M_node->_M_prev; + return __tmp; + } + + [[__nodiscard__]] + friend constexpr bool + operator==(const _Iterator& __x, const _Iterator& __y) noexcept + { return __x._M_node == __y._M_node; } + +#if __cpp_impl_three_way_comparison < 201907L + [[__nodiscard__]] + friend constexpr bool + operator!=(const _Iterator& __x, const _Iterator& __y) noexcept + { return __x._M_node != __y._M_node; } +#endif + private: - _List_node_base* _M_base() { return this; } + template<typename _Tp, typename _Allocator> + friend class _GLIBCXX_STD_C::list; + + friend _Iterator<!_Const, _Ptr>; + + constexpr _Iterator<false, _Ptr> + _M_const_cast() const noexcept + { return _Iterator<false, _Ptr>(_M_node); } + + _Base_ptr _M_node; + }; +} // namespace __list +#endif // USE_ALLOC_PTR_FOR_LIST + +_GLIBCXX_BEGIN_NAMESPACE_CONTAINER + template<typename _Tp> struct _List_node; + template<typename _Tp> struct _List_iterator; + template<typename _Tp> struct _List_const_iterator; +_GLIBCXX_END_NAMESPACE_CONTAINER + +namespace __list +{ + // Determine the node and iterator types used by std::list. + template<typename _Tp, typename _Ptr> + struct _Node_traits; + +#if _GLIBCXX_USE_ALLOC_PTR_FOR_LIST <= 9000 + // Specialization for the simple case where the allocator's pointer type + // is the same type as value_type*. + // For ABI compatibility we can't change the types used for this case. + template<typename _Tp> + struct _Node_traits<_Tp, _Tp*> + { + typedef __detail::_List_node_base _Node_base; + typedef __detail::_List_node_header _Node_header; + typedef _GLIBCXX_STD_C::_List_node<_Tp> _Node; + typedef _GLIBCXX_STD_C::_List_iterator<_Tp> _Iterator; + typedef _GLIBCXX_STD_C::_List_const_iterator<_Tp> _Const_iterator; + }; +#endif + +#if ! _GLIBCXX_USE_ALLOC_PTR_FOR_LIST + // Always use the T* specialization. + template<typename _Tp, typename _Ptr> + struct _Node_traits + : _Node_traits<_Tp, _Tp*> + { }; +#else + // Primary template used when the allocator uses fancy pointers. + template<typename _Tp, typename _Ptr> + struct _Node_traits + { + private: + using _VoidPtr = __ptr_rebind<_Ptr, void>; + using _ValPtr = __ptr_rebind<_Ptr, _Tp>; + + public: + using _Node_base = __list::_Node_base<_VoidPtr>; + using _Node_header = __list::_Node_header<_VoidPtr>; + using _Node = __list::_Node<_ValPtr>; + using _Iterator = __list::_Iterator<false, _ValPtr>; + using _Const_iterator = __list::_Iterator<true, _ValPtr>; }; +#endif // USE_ALLOC_PTR_FOR_LIST - // Used by list::sort to hold nodes being sorted. - struct _Scratch_list : _List_node_base + // Used by std::list::sort to hold nodes being sorted. + template<typename _NodeBaseT> + struct _Scratch_list + : _NodeBaseT { - _Scratch_list() { _M_next = _M_prev = this; } + typedef _NodeBaseT _Base; + typedef typename _Base::_Base_ptr _Base_ptr; - bool empty() const { return _M_next == this; } + _Scratch_list() { this->_M_next = this->_M_prev = this->_M_base(); } - void swap(_List_node_base& __l) { _List_node_base::swap(*this, __l); } + bool empty() const { return this->_M_next == this->_M_base(); } + + void swap(_Base& __l) { _Base::swap(*this, __l); } template<typename _Iter, typename _Cmp> struct _Ptr_cmp @@ -177,8 +484,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Cmp _M_cmp; bool - operator()(__detail::_List_node_base* __lhs, - __detail::_List_node_base* __rhs) /* not const */ + operator()(_Base_ptr __lhs, _Base_ptr __rhs) /* not const */ { return _M_cmp(*_Iter(__lhs), *_Iter(__rhs)); } }; @@ -186,26 +492,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION struct _Ptr_cmp<_Iter, void> { bool - operator()(__detail::_List_node_base* __lhs, - __detail::_List_node_base* __rhs) const + operator()(_Base_ptr __lhs, _Base_ptr __rhs) const { return *_Iter(__lhs) < *_Iter(__rhs); } }; // Merge nodes from __x into *this. Both lists must be sorted wrt _Cmp. template<typename _Cmp> void - merge(_List_node_base& __x, _Cmp __comp) + merge(_Base& __x, _Cmp __comp) { - _List_node_base* __first1 = _M_next; - _List_node_base* const __last1 = this; - _List_node_base* __first2 = __x._M_next; - _List_node_base* const __last2 = std::__addressof(__x); + _Base_ptr __first1 = this->_M_next; + _Base_ptr const __last1 = this->_M_base(); + _Base_ptr __first2 = __x._M_next; + _Base_ptr const __last2 = __x._M_base(); while (__first1 != __last1 && __first2 != __last2) { if (__comp(__first2, __first1)) { - _List_node_base* __next = __first2->_M_next; + _Base_ptr __next = __first2->_M_next; __first1->_M_transfer(__first2, __next); __first2 = __next; } @@ -217,18 +522,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } // Splice the node at __i into *this. - void _M_take_one(_List_node_base* __i) + void _M_take_one(_Base_ptr __i) { this->_M_transfer(__i, __i->_M_next); } // Splice all nodes from *this after __i. - void _M_put_all(_List_node_base* __i) + void _M_put_all(_Base_ptr __i) { if (!empty()) - __i->_M_transfer(_M_next, this); + __i->_M_transfer(this->_M_next, this->_M_base()); } }; - - } // namespace detail +} // namespace __list _GLIBCXX_BEGIN_NAMESPACE_CONTAINER @@ -236,6 +540,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER template<typename _Tp> struct _List_node : public __detail::_List_node_base { + typedef _List_node* _Node_ptr; + #if __cplusplus >= 201103L __gnu_cxx::__aligned_membuf<_Tp> _M_storage; _Tp* _M_valptr() { return _M_storage._M_ptr(); } @@ -245,6 +551,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER _Tp* _M_valptr() { return std::__addressof(_M_data); } _Tp const* _M_valptr() const { return std::__addressof(_M_data); } #endif + + _Node_ptr _M_node_ptr() { return this; } }; /** @@ -433,14 +741,17 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type; typedef __gnu_cxx::__alloc_traits<_Tp_alloc_type> _Tp_alloc_traits; + + typedef __list::_Node_traits<_Tp, typename _Tp_alloc_traits::pointer> + _Node_traits; typedef typename _Tp_alloc_traits::template - rebind<_List_node<_Tp> >::other _Node_alloc_type; + rebind<typename _Node_traits::_Node>::other _Node_alloc_type; typedef __gnu_cxx::__alloc_traits<_Node_alloc_type> _Node_alloc_traits; struct _List_impl : public _Node_alloc_type { - __detail::_List_node_header _M_node; + typename _Node_traits::_Node_header _M_node; _List_impl() _GLIBCXX_NOEXCEPT_IF( is_nothrow_default_constructible<_Node_alloc_type>::value) @@ -486,9 +797,42 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 _M_get_node() { return _Node_alloc_traits::allocate(_M_impl, 1); } +#if __cplusplus < 201103L || _GLIBCXX_USE_ALLOC_PTR_FOR_LIST void _M_put_node(typename _Node_alloc_traits::pointer __p) _GLIBCXX_NOEXCEPT { _Node_alloc_traits::deallocate(_M_impl, __p, 1); } +#else + void + _M_put_node(_List_node<_Tp>* __p) + { + // When not using the allocator's pointer type internally we must + // convert between _Node_ptr (i.e. _List_node*) and the allocator's + // pointer type. + using __alloc_pointer = typename _Node_alloc_traits::pointer; + auto __ap = pointer_traits<__alloc_pointer>::pointer_to(*__p); + _Node_alloc_traits::deallocate(_M_impl, __ap, 1); + } +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr + void + _M_destroy_node(typename _Node_alloc_traits::pointer __p) + { +#if __cplusplus >= 201103L + // Destroy the element + _Node_alloc_traits::destroy(_M_impl, __p->_M_valptr()); + // Only destroy the node if the pointers require it. + using _Node = typename _Node_traits::_Node; + using _Base_ptr = typename _Node_traits::_Node_base::_Base_ptr; + if constexpr (!is_trivially_destructible<_Base_ptr>::value) + __p->~_Node(); +#else + _Tp_alloc_type(_M_impl).destroy(__p->_M_valptr()); +#endif + this->_M_put_node(__p); + } +#pragma GCC diagnostic pop public: typedef _Alloc allocator_type; @@ -548,13 +892,24 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 // so that explicit instantiation declarations of std::list that were // compiled with old versions of GCC can still find these old symbols. + // Use 'if constexpr' so that the functions don't do anything for + // specializations using _Node_traits<T, fancy-pointer>, because any + // old code referencing these symbols wasn't using the fancy-pointer + // specializations. + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr + # if __cplusplus >= 201103L _List_base(_List_base&& __x, _Node_alloc_type&& __a) : _M_impl(std::move(__a)) { - if (__x._M_get_Node_allocator() == _M_get_Node_allocator()) - _M_move_nodes(std::move(__x)); - // else caller must move individual elements. +#if _GLIBCXX_USE_ALLOC_PTR_FOR_LIST + if constexpr (is_same<typename _Tp_alloc_traits::pointer, _Tp*>::value) +#endif + if (__x._M_get_Node_allocator() == _M_get_Node_allocator()) + _M_move_nodes(std::move(__x)); + // else caller must move individual elements. } # endif @@ -562,13 +917,20 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 _S_distance(const __detail::_List_node_base* __first, const __detail::_List_node_base* __last) { - size_t __n = 0; - while (__first != __last) +#if _GLIBCXX_USE_ALLOC_PTR_FOR_LIST + if constexpr (!is_same<typename _Tp_alloc_traits::pointer, _Tp*>::value) + return 0; + else +#endif { - __first = __first->_M_next; - ++__n; + size_t __n = 0; + while (__first != __last) + { + __first = __first->_M_next; + ++__n; + } + return __n; } - return __n; } #if _GLIBCXX_USE_CXX11_ABI @@ -585,10 +947,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 // count the number of nodes size_t _M_node_count() const { - return _S_distance(_M_impl._M_node._M_next, - std::__addressof(_M_impl._M_node)); + return _S_distance(_M_impl._M_node._M_next, _M_impl._M_node._M_base()); } #endif +#pragma GCC diagnostic pop #endif // ! INLINE_VERSION }; @@ -664,6 +1026,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 typedef typename _Base::_Tp_alloc_traits _Tp_alloc_traits; typedef typename _Base::_Node_alloc_type _Node_alloc_type; typedef typename _Base::_Node_alloc_traits _Node_alloc_traits; + typedef typename _Base::_Node_traits _Node_traits; public: typedef _Tp value_type; @@ -671,8 +1034,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 typedef typename _Tp_alloc_traits::const_pointer const_pointer; typedef typename _Tp_alloc_traits::reference reference; typedef typename _Tp_alloc_traits::const_reference const_reference; - typedef _List_iterator<_Tp> iterator; - typedef _List_const_iterator<_Tp> const_iterator; + typedef typename _Node_traits::_Iterator iterator; + typedef typename _Node_traits::_Const_iterator const_iterator; typedef std::reverse_iterator<const_iterator> const_reverse_iterator; typedef std::reverse_iterator<iterator> reverse_iterator; typedef size_t size_type; @@ -682,7 +1045,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 protected: // Note that pointers-to-_Node's can be ctor-converted to // iterator types. - typedef _List_node<_Tp> _Node; + typedef typename _Node_alloc_traits::pointer _Node_ptr; using _Base::_M_impl; using _Base::_M_put_node; @@ -696,10 +1059,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 * @a __args in it. */ #if __cplusplus < 201103L - _Node* + _Node_ptr _M_create_node(const value_type& __x) { - _Node* __p = this->_M_get_node(); + _Node_ptr __p = this->_M_get_node(); __try { _Tp_alloc_type __alloc(_M_get_Node_allocator()); @@ -714,16 +1077,19 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 } #else template<typename... _Args> - _Node* + _Node_ptr _M_create_node(_Args&&... __args) { - auto __p = this->_M_get_node(); auto& __alloc = _M_get_Node_allocator(); - __allocated_ptr<_Node_alloc_type> __guard{__alloc, __p}; - _Node_alloc_traits::construct(__alloc, __p->_M_valptr(), + auto __guard = std::__allocate_guarded_obj(__alloc); + _Node_alloc_traits::construct(__alloc, __guard->_M_valptr(), std::forward<_Args>(__args)...); - __guard = nullptr; + auto __p = __guard.release(); +#if _GLIBCXX_USE_ALLOC_PTR_FOR_LIST return __p; +#else + return std::__to_address(__p); +#endif } #endif @@ -1093,7 +1459,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 _GLIBCXX_NODISCARD iterator end() _GLIBCXX_NOEXCEPT - { return iterator(&this->_M_impl._M_node); } + { return iterator(this->_M_impl._M_node._M_base()); } /** * Returns a read-only (constant) iterator that points one past @@ -1103,7 +1469,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 _GLIBCXX_NODISCARD const_iterator end() const _GLIBCXX_NOEXCEPT - { return const_iterator(&this->_M_impl._M_node); } + { return const_iterator(this->_M_impl._M_node._M_base()); } /** * Returns a read/write reverse iterator that points to the last @@ -1164,7 +1530,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 [[__nodiscard__]] const_iterator cend() const noexcept - { return const_iterator(&this->_M_impl._M_node); } + { return const_iterator(this->_M_impl._M_node._M_base()); } /** * Returns a read-only (constant) reverse iterator that points to @@ -1194,7 +1560,9 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 */ _GLIBCXX_NODISCARD bool empty() const _GLIBCXX_NOEXCEPT - { return this->_M_impl._M_node._M_next == &this->_M_impl._M_node; } + { + return this->_M_impl._M_node._M_next == this->_M_impl._M_node._M_base(); + } /** Returns the number of elements in the %list. */ _GLIBCXX_NODISCARD @@ -1671,8 +2039,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 void swap(list& __x) _GLIBCXX_NOEXCEPT { - __detail::_List_node_base::swap(this->_M_impl._M_node, - __x._M_impl._M_node); + _Node_traits::_Node_base::swap(this->_M_impl._M_node, + __x._M_impl._M_node); size_t __xsize = __x._M_get_size(); __x._M_set_size(this->_M_get_size()); @@ -2064,7 +2432,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 void _M_insert(iterator __position, const value_type& __x) { - _Node* __tmp = _M_create_node(__x); + _Node_ptr __tmp = _M_create_node(__x); __tmp->_M_hook(__position._M_node); this->_M_inc_size(1); } @@ -2073,7 +2441,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 void _M_insert(iterator __position, _Args&&... __args) { - _Node* __tmp = _M_create_node(std::forward<_Args>(__args)...); + _Node_ptr __tmp = _M_create_node(std::forward<_Args>(__args)...); __tmp->_M_hook(__position._M_node); this->_M_inc_size(1); } @@ -2083,16 +2451,11 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 void _M_erase(iterator __position) _GLIBCXX_NOEXCEPT { + typedef typename _Node_traits::_Node _Node; this->_M_dec_size(1); __position._M_node->_M_unhook(); - _Node* __n = static_cast<_Node*>(__position._M_node); -#if __cplusplus >= 201103L - _Node_alloc_traits::destroy(_M_get_Node_allocator(), __n->_M_valptr()); -#else - _Tp_alloc_type(_M_get_Node_allocator()).destroy(__n->_M_valptr()); -#endif - - _M_put_node(__n); + _Node& __n = static_cast<_Node&>(*__position._M_node); + this->_M_destroy_node(__n._M_node_ptr()); } // To implement the splice (and merge) bits of N1599. @@ -2359,6 +2722,113 @@ _GLIBCXX_END_NAMESPACE_CONTAINER } return __n; } + +#if _GLIBCXX_USE_ALLOC_PTR_FOR_LIST + template<bool _Const, typename _Ptr> + inline ptrdiff_t + __distance(__list::_Iterator<_Const, _Ptr> __first, + __list::_Iterator<_Const, _Ptr> __last, + input_iterator_tag __tag) + { + using _Tp = typename __list::_Iterator<_Const, _Ptr>::value_type; + using _Sentinel = typename __list::_Node_traits<_Tp, _Ptr>::_Node_header; + auto __beyond = __last; + ++__beyond; + const bool __whole = __first == __beyond; + if (__builtin_constant_p (__whole) && __whole) + return static_cast<const _Sentinel&>(*__last._M_node)._M_size; + + ptrdiff_t __n = 0; + while (__first != __last) + { + ++__first; + ++__n; + } + return __n; + } +#endif +#endif + +#if _GLIBCXX_USE_ALLOC_PTR_FOR_LIST +namespace __list +{ + template<typename _VoidPtr> + void + _Node_base<_VoidPtr>::swap(_Node_base& __x, _Node_base& __y) noexcept + { + auto __px = pointer_traits<_Base_ptr>::pointer_to(__x); + auto __py = pointer_traits<_Base_ptr>::pointer_to(__y); + + if (__x._M_next != __px) + { + if (__y._M_next != __py) + { + using std::swap; + // Both __x and __y are not empty. + swap(__x._M_next,__y._M_next); + swap(__x._M_prev,__y._M_prev); + __x._M_next->_M_prev = __x._M_prev->_M_next = __px; + __y._M_next->_M_prev = __y._M_prev->_M_next = __py; + } + else + { + // __x is not empty, __y is empty. + __y._M_next = __x._M_next; + __y._M_prev = __x._M_prev; + __y._M_next->_M_prev = __y._M_prev->_M_next = __py; + __x._M_next = __x._M_prev = __px; + } + } + else if (__y._M_next != __py) + { + // __x is empty, __y is not empty. + __x._M_next = __y._M_next; + __x._M_prev = __y._M_prev; + __x._M_next->_M_prev = __x._M_prev->_M_next = __px; + __y._M_next = __y._M_prev = __py; + } + } + + template<typename _VoidPtr> + void + _Node_base<_VoidPtr>::_M_transfer(_Base_ptr const __first, + _Base_ptr const __last) + { + __glibcxx_assert(__first != __last); + + auto __self = pointer_traits<_Base_ptr>::pointer_to(*this); + if (__self != __last) + { + // Remove [first, last) from its old position. + __last->_M_prev->_M_next = __self; + __first->_M_prev->_M_next = __last; + this->_M_prev->_M_next = __first; + + // Splice [first, last) into its new position. + auto const __tmp = this->_M_prev; + this->_M_prev = __last->_M_prev; + __last->_M_prev = __first->_M_prev; + __first->_M_prev = __tmp; + } + } + + template<typename _VoidPtr> + void + _Node_header<_VoidPtr>::_M_reverse() noexcept + { + const auto __self = this->_M_base(); + auto __tmp = __self; + do + { + using std::swap; + swap(__tmp->_M_next, __tmp->_M_prev); + + // Old next node is now prev. + __tmp = __tmp->_M_prev; + } + while (__tmp != __self); + } +} // namespace __list #endif _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/testsuite/23_containers/list/capacity/29134.cc b/libstdc++-v3/testsuite/23_containers/list/capacity/29134.cc index 956afe19dc05..69540d3703b6 100644 --- a/libstdc++-v3/testsuite/23_containers/list/capacity/29134.cc +++ b/libstdc++-v3/testsuite/23_containers/list/capacity/29134.cc @@ -35,6 +35,11 @@ void test01() std::allocator<_List_node<int> > a; VERIFY( l.max_size() == __gnu_test::max_size(a) ); + +#if _GLIBCXX_LIST_USE_ALLOC_PTR + std::allocator<std::__list::_Node<int*>> b; + VERIFY( __gnu_test::max_size(b) == __gnu_test::max_size(a) ); +#endif } int main() diff --git a/libstdc++-v3/testsuite/23_containers/list/capacity/node_sizes.cc b/libstdc++-v3/testsuite/23_containers/list/capacity/node_sizes.cc new file mode 100644 index 000000000000..8394824ba286 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/list/capacity/node_sizes.cc @@ -0,0 +1,24 @@ +// { dg-do compile { target c++11 } } + +#include <list> + +#if _GLIBCXX_LIST_USE_ALLOC_PTR + +#ifdef _GLIBCXX_DEBUG +namespace C = std::_GLIBCXX_STD_C; +#else +namespace C = std; +#endif + +// We use double here because for ADJUST_FIELD_ALIGN targets (like i386) +// its alignment differs when used as a data member or as a complete object. +static_assert(sizeof(C::_List_node<double>) + == sizeof(std::__list::_Node<double*>), + "node types have same size"); +static_assert(alignof(C::_List_node<double>) + == alignof(std::__list::_Node<double*>), + "node types have same alignment"); +static_assert(__alignof(C::_List_node<double>) + == __alignof(std::__list::_Node<double*>), + "node types have same preferred alignment"); +#endif diff --git a/libstdc++-v3/testsuite/23_containers/list/requirements/completeness.cc b/libstdc++-v3/testsuite/23_containers/list/requirements/completeness.cc new file mode 100644 index 000000000000..ad359e3b51d0 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/list/requirements/completeness.cc @@ -0,0 +1,19 @@ +// { dg-do compile } + +// C++17 [list.overview] +// An incomplete type T may be used when instantiating list if the allocator +// satisfies the allocator completeness requirements (20.5.3.5.1). +// T shall be complete before any member of the resulting specialization +// of list is referenced. + +#include <list> + +struct Incomplete; + +// This instantiates std::list, but none of its members. +const int sz = sizeof(std::list<Incomplete>); + +// Technically the following references a member of std::list, but because +// our iterators are SCARY it doesn't instantiate any members of std::list. +// GCC's own source code expects this to work. +std::list<Incomplete>::iterator i = std::list<Incomplete>::iterator(); diff --git a/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr.cc b/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr.cc new file mode 100644 index 000000000000..d3b2cfe6d923 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr.cc @@ -0,0 +1,87 @@ +// { dg-do compile { target c++11 } } + +#include <list> +#include <testsuite_allocator.h> + +// An allocator that uses __gnu_cxx::_Pointer_adapter as its pointer type. +template class std::list<int, __gnu_test::CustomPointerAlloc<int>>; + +// Unlike __gnu_cxx::_Pointer_adapter, this fancy pointer supports neither +// implicit nor explicit conversions from raw pointers. The constructor from +// a raw pointer is explicit and requires a second parameter. The only way for +// containers to construct one of these pointers is pointer_traits::pointer_to. +template<typename T> +struct Pointer : __gnu_test::PointerBase<Pointer<T>, T> +{ + using Base = __gnu_test::PointerBase<Pointer<T>, T>; + + Pointer() = default; + Pointer(std::nullptr_t) : Base() { } + explicit Pointer(T* p, int) : Base(p) { } + + // Allow conversions to const_pointer and to void_pointer + template<typename U, typename = typename std::enable_if< + (!std::is_const<U>::value && std::is_same<T, const U>::value) + || (std::is_void<T>::value && std::is_convertible<U*, T*>::value) + >::type> + Pointer(const Pointer<U>& p) : Base(p.operator->()) { } + + template<typename U> + static typename std::enable_if<std::is_same<U, T>::value, Pointer>::type + pointer_to(U& t) + { return Pointer(std::addressof(t), 1); } +}; + +// A minimal allocator that uses Pointer as its pointer type. +template<typename T> +struct Allocator +{ + using value_type = T; + using pointer = Pointer<T>; + + Allocator() = default; + template<typename U> + Allocator(const Allocator<U>&) { } + + pointer allocate(std::size_t n) + { return pointer(std::allocator<T>().allocate(n), 1); } + + void deallocate(pointer p, std::size_t n) + { + std::allocator<T>().deallocate(p.operator->(), n); + } + + bool operator==(const Allocator&) const { return true; } + bool operator!=(const Allocator&) const { return false; } +}; + +template class std::list<int, Allocator<int>>; + +#include <testsuite_iterators.h> + +void +test_template_members(__gnu_test::input_container<short>& c) +{ + // Use member functions that are not included in explicit instantiations. + std::list<int, Allocator<int>> l(c.begin(), c.end()); + l.assign(c.begin(), c.end()); + l.insert(l.begin(), c.begin(), c.end()); + l.emplace_front(1); + l.emplace_back(1); + l.emplace(l.begin(), 1); + l.remove_if([](int) { return false; }); + l.unique([](int, int) { return false; }); + l.merge(l, [](int, int) { return false; }); + l.merge(std::move(l), [](int, int) { return false; }); + l.sort([](int, int) { return false; }); + +#ifdef __glibcxx_ranges_to_container + short arr[2]; + __gnu_test::test_input_range<short> r(arr); + std::list<int, Allocator<int>> l2(std::from_range, r); + l2.assign_range(r); + l2.prepend_range(r); + l2.append_range(r); + l2.insert_range(l2.begin(), r); +#endif +} diff --git a/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr_ignored.cc b/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr_ignored.cc new file mode 100644 index 000000000000..e6a499d27160 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr_ignored.cc @@ -0,0 +1,4 @@ +// { dg-options "-D_GLIBCXX_LIST_USE_ALLOC_PTR=0" } +// { dg-do compile { target c++11 } } + +#include "alloc_ptr.cc"