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_LIST_USE_ALLOC_PTR=0 will force std::list to have the old, non-conforming behaviour and use raw pointers internally. This macro is currently undocumented, which needs to be fixed. Pretty printers for std::list need to be updated to handle the new node types. libstdc++-v3/ChangeLog: PR libstdc++/57272 PR libstdc++/110952 * include/bits/list.tcc (_List_base::_M_clear()): Replace uses of raw pointers. (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_LIST_USE_ALLOC_PTR): 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_get_node, _List_base::_M_put_node): Convert to/from _List_node<T>* if required. (_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*. (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, list::_M_erase): Use _Node_ptr typedef instead of _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/requirements/explicit_instantiation/alloc_ptr.cc: New test. * testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr_ignored.cc: New test. --- Tested x86_64-linux. Pull request at https://forge.sourceware.org/gcc/gcc-TEST/pulls/25 libstdc++-v3/include/bits/list.tcc | 42 +- libstdc++-v3/include/bits/stl_list.h | 552 ++++++++++++++++-- .../explicit_instantiation/alloc_ptr.cc | 76 +++ .../alloc_ptr_ignored.cc | 4 + 4 files changed, 608 insertions(+), 66 deletions(-) create mode 100644 libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr.cc create mode 100644 libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr_ignored.cc diff --git a/libstdc++-v3/include/bits/list.tcc b/libstdc++-v3/include/bits/list.tcc index 65835c1379f..58892f78c8f 100644 --- a/libstdc++-v3/include/bits/list.tcc +++ b/libstdc++-v3/include/bits/list.tcc @@ -66,11 +66,12 @@ _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::_Node_ptr _Node_ptr; + typename _Node::_Base_ptr __cur = _M_impl._M_node._M_next; + while (__cur != _M_impl._M_node._M_base()) { - _Node* __tmp = static_cast<_Node*>(__cur); + _Node_ptr __tmp = static_cast<_Node&>(*__cur)._M_node_ptr(); __cur = __tmp->_M_next; _Tp* __val = __tmp->_M_valptr(); #if __cplusplus >= 201103L @@ -89,10 +90,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 +106,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 +483,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 +503,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 +618,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 b1ab335ba4c..4628c782db6 100644 --- a/libstdc++-v3/include/bits/stl_list.h +++ b/libstdc++-v3/include/bits/stl_list.h @@ -62,6 +62,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 @@ -69,6 +70,13 @@ # include <bits/ranges_util.h> // ranges::subrange #endif +#if __cplusplus < 201103L +#undef _GLIBCXX_LIST_USE_ALLOC_PTR +#define _GLIBCXX_LIST_USE_ALLOC_PTR 0 +#elif ! defined _GLIBCXX_LIST_USE_ALLOC_PTR +#define _GLIBCXX_LIST_USE_ALLOC_PTR 1 +#endif + namespace std _GLIBCXX_VISIBILITY(default) { _GLIBCXX_BEGIN_NAMESPACE_VERSION @@ -84,6 +92,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; @@ -102,6 +112,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 @@ -112,7 +125,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif }; - /// The %list node header. struct _List_node_header : public _List_node_base, _List_size { @@ -157,18 +169,302 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _List_size::operator=(_List_size()); } - private: - _List_node_base* _M_base() { return this; } + using _List_node_base::_M_base; +#if ! _GLIBCXX_INLINE_VERSION + _List_node_base* _M_base() { return this; } // XXX GLIBCXX_ABI Deprecated +#endif }; - // Used by list::sort to hold nodes being sorted. - struct _Scratch_list : _List_node_base + } // namespace detail + +_GLIBCXX_BEGIN_NAMESPACE_CONTAINER + template<typename _Tp> struct _List_node; + template<typename _Tp> struct _List_iterator; + template<typename _Tp> struct _List_const_iterator; +_GLIBCXX_BEGIN_NAMESPACE_CXX11 + template<typename _Tp, typename _Allocator> class list; +_GLIBCXX_END_NAMESPACE_CXX11 +_GLIBCXX_END_NAMESPACE_CONTAINER + +namespace __list +{ + // Determine the node and iterator types used by std::list. + template<typename _Tp, typename _Ptr> + struct _Node_traits; + + // 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*> { - _Scratch_list() { _M_next = _M_prev = this; } + 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; + }; - bool empty() const { return _M_next == this; } +#if ! _GLIBCXX_LIST_USE_ALLOC_PTR + // Always use the T* specialization. + template<typename _Tp, typename _Ptr> + struct _Node_traits + : _Node_traits<_Tp, _Tp*> + { }; +#else - void swap(_List_node_base& __l) { _List_node_base::swap(*this, __l); } + // 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; + + // 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>; + }; + + template<bool _Const, typename _Ptr> + class _Iterator + { + using _Node = __list::_Node<_Ptr>; + using _Base_ptr = typename _Node::_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>> + _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: + template<typename _Tp, typename _Allocator> + friend class _GLIBCXX_STD_C::list; + + constexpr _Iterator<false, _Ptr> + _M_const_cast() const noexcept + { return _Iterator<false, _Ptr>(_M_node); } + + friend _Iterator<!_Const, _Ptr>; + + _Base_ptr _M_node; + }; +#endif // LIST_USE_ALLOC_PTR + + // Used by std::list::sort to hold nodes being sorted. + template<typename _NodeBaseT> + struct _Scratch_list + : _NodeBaseT + { + typedef _NodeBaseT _Base; + typedef typename _Base::_Base_ptr _Base_ptr; + + _Scratch_list() { this->_M_next = this->_M_prev = this->_M_base(); } + + 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 @@ -176,8 +472,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)); } }; @@ -185,26 +480,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; } @@ -216,18 +510,19 @@ _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 @@ -235,6 +530,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(); } @@ -244,6 +541,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; } }; /** @@ -432,14 +731,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) @@ -481,6 +783,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 void _M_dec_size(size_t) { } #endif +#if __cplusplus < 201103L || _GLIBCXX_LIST_USE_ALLOC_PTR typename _Node_alloc_traits::pointer _M_get_node() { return _Node_alloc_traits::allocate(_M_impl, 1); } @@ -488,6 +791,22 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 void _M_put_node(typename _Node_alloc_traits::pointer __p) _GLIBCXX_NOEXCEPT { _Node_alloc_traits::deallocate(_M_impl, __p, 1); } +#else + // 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. We do that here. + _List_node<_Tp>* + _M_get_node() + { return std::__to_address(_Node_alloc_traits::allocate(_M_impl, 1)); } + + void + _M_put_node(_List_node<_Tp>* __p) + { + 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 public: typedef _Alloc allocator_type; @@ -547,13 +866,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_LIST_USE_ALLOC_PTR + 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 @@ -561,13 +891,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_LIST_USE_ALLOC_PTR + 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 @@ -584,10 +921,16 @@ _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)); +#if _GLIBCXX_LIST_USE_ALLOC_PTR + if constexpr (!is_same<typename _Tp_alloc_traits::pointer, _Tp*>::value) + return 0; + else +#endif + return _S_distance(_M_impl._M_node._M_next, + _M_impl._M_node._M_base()); } #endif +#pragma GCC diagnostic pop #endif // ! INLINE_VERSION }; @@ -663,6 +1006,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; @@ -670,8 +1014,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; @@ -681,7 +1025,8 @@ _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_traits::_Node _Node; + typedef typename _Node::_Node_ptr _Node_ptr; using _Base::_M_impl; using _Base::_M_put_node; @@ -695,10 +1040,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()); @@ -713,7 +1058,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 } #else template<typename... _Args> - _Node* + _Node_ptr _M_create_node(_Args&&... __args) { auto __p = this->_M_get_node(); @@ -1070,7 +1415,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 @@ -1080,7 +1425,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 @@ -1141,7 +1486,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 @@ -1171,7 +1516,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 @@ -1682,8 +2029,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()); @@ -2105,7 +2452,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); } @@ -2114,7 +2461,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); } @@ -2126,7 +2473,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 { this->_M_dec_size(1); __position._M_node->_M_unhook(); - _Node* __n = static_cast<_Node*>(__position._M_node); + _Node_ptr __n = static_cast<_Node&>(*__position._M_node)._M_node_ptr(); #if __cplusplus >= 201103L _Node_alloc_traits::destroy(_M_get_Node_allocator(), __n->_M_valptr()); #else @@ -2397,6 +2744,113 @@ _GLIBCXX_END_NAMESPACE_CONTAINER } return __n; } + +#if _GLIBCXX_LIST_USE_ALLOC_PTR + 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_LIST_USE_ALLOC_PTR +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/requirements/explicit_instantiation/alloc_ptr.cc b/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr.cc new file mode 100644 index 00000000000..01715b599a9 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/list/requirements/explicit_instantiation/alloc_ptr.cc @@ -0,0 +1,76 @@ +// { 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.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; }); +} 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 00000000000..e6a499d2716 --- /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" -- 2.47.0