On Sat, 30 Nov 2024, 13:52 François Dumont, <frs.dum...@gmail.com> wrote:
> Hi > > I've applied all your comments below and the ones you did on the PR > directly. > > When all new types totally seperated from the legacy types > _Rb_tree_node_traits is indeed useless. > > Regarding _Rb_tree_helpers I got rid of it but move the insert-and-rebalance > and rebalance-for-erase function to _Node_traits<>. > > PR updated: > > https://forge.sourceware.org/gcc/gcc-TEST/pulls/27 > > libstdc++: Add fancy pointer support in map and set Support fancy > allocator pointer type in std::_Rb_tree<>. In case of fancy pointer type > the container is now storing the pointer to __rb_tree::_Node<_ValPtr> as > a pointer to __rb_tree::_Node_base<_VoidPtr>. Many methods are adapted to > take and return _Base_ptr in place of _Link_type which has been renamed > into _Node_ptr. As all node are stored as _Base_ptr have all methods > working with this type and remove _Const_Base_ptr and _Const_Node_ptr and > all methods associated with it. > Sounds good! I'll take another look on Monday. libstdc++-v3/ChangeLog: * include/bits/stl_tree.h > [_GLIBCXX_USE_ALLOC_PTR_FOR_RB_TREE]: New macro to control usage of the > code required to support fancy allocator pointer type. (_Rb_tree_node_ > base::_Const_Base_ptr): Remove. (_Rb_tree_node_base::_S_minimum, > _Rb_tree_node_base::_S_maximum): Remove overloads for _Const_Base_ptr. > (_Rb_tree_node_base::_M_base_ptr()): New. > (_Rb_tree_node_base::_M_node_ptr<_NodePtr>()): > New. (_Rb_tree_node::_Link_type): Rename into... (_Rb_tree_node::_Node_ptr): > ...this. (__rb_tree::_Node_base<>): New. (__rb_tree::_Header<>): New. > (__rb_tree::_Node<>): New. (_Rb_tree_increment(const > _Rb_tree_node_base*)): Remove declaration. (_Rb_tree_decrement(const > _Rb_tree_node_base*)): Remove declaration. > (_Rb_tree_iterator<>::_Link_type): Rename into... > (_Rb_tree_iterator<>::_Node_ptr): ...this. > (_Rb_tree_const_iterator<>::_Link_type): Rename into... > (_Rb_tree_const_iterator<>::_Node_ptr): ...this. > (_Rb_tree_const_iterator<>::_M_const_cast): Remove. > (_Rb_tree_const_iterator<>::_M_node): Change type into _Base_ptr. (__rb_ > tree::_Iterator<>): New. (__rb_tree::_Node_traits<>): New. > (_Rb_tree<>::_Node_base, _Rb_tree::_Node_type): New. > (_Rb_tree<>::_Link_type): Rename into... (_Rb_tree<>::_Node_ptr): ...this. > (_Rb_tree<>::_Const_Base_ptr, _Rb_tree<>::_Const_Node_ptr): Remove. > (_Rb_tree<>): Adapt to generalize usage of _Base_ptr in place of former > _Link_type now _Node_ptr. (_Rb_tree<>::_M_mbegin): Remove. > (_Rb_tree<>::_S_left(_Const_Base_ptr)): Remove. > (_Rb_tree<>::_S_right(_Const_Base_ptr)): Remove. > (_Rb_tree<>::_S_maximum(_Const_Base_ptr)): Remove. > (_Rb_tree<>::_S_minimum(_Const_Base_ptr)): Remove. * > testsuite/23_containers/map/allocator/ext_ptr.cc: New test case. * > testsuite/23_containers/multimap/allocator/ext_ptr.cc: New test case. * > testsuite/23_containers/multiset/allocator/ext_ptr.cc: New test case. * > testsuite/23_containers/set/allocator/ext_ptr.cc: New test case. > > I've tested it in C++98/C++17/C++20 with default > _GLIBCXX_USE_ALLOC_PTR_FOR_RB_TREE and with it 9001. > > François > On 18/11/2024 17:39, Jonathan Wakely wrote: > > On 18/11/24 06:57 +0100, François Dumont wrote: > > Hi > > Here is a new proposal with all the cleanup regarding _Const_Base_ptr > that makes support of allocator's fancy pointer type simpler. > > Also submitted as PR: > https://forge.sourceware.org/gcc/gcc-TEST/pulls/27 > > libstdc++: Add fancy pointer support in map and set > > Support fancy allocator pointer type in std::_Rb_tree<>. > > In case of fancy pointer type the container is now storing the > pointer to > _Rb_tree_pnode<> as a pointer to _Rb_tree_pnode_base<>. > > Many methods are adapted to take and return _Base_ptr in place of > _Link_type > which has been renamed into _Node_ptr. > > As all node are stored as _Base_ptr have all methods working with > this type > and remove _Const_Base_ptr and all methods associated to it. > > libstdc++-v3/ChangeLog: > > * include/bits/stl_set.h (std::set<>::upper_bound<>(const > _Kt&) const): Fix > decltype typo to use const_iterator. > * include/bits/stl_tree.h > (_Rb_tree_ptr_traits<>): New. > (_Rb_tree_pnode_base<>): New. > (_Rb_tree_node_base): Inherit from latter. > (_Rb_tree_node_base::_Const_Base_ptr): Remove. > (_Rb_tree_node_base::_S_minimum(_Const_Base_ptr)): Remove. > (_Rb_tree_node_base::_S_maximum(_Const_Base_ptr)): Remove. > (_Rb_tree_pheader): New. > (_Rb_tree_header): Inherit from latter. > (_Rb_tree_node_val): New. > (_Rb_tree_node): Inherit from latter. > (_Rb_tree_pnode): New. > (_Rb_tree_iterator<>::_Link_type): Rename into... > (_Rb_tree_iterator<>::_Node_ptr): ...this. > (_Rb_tree_const_iterator<>::_Link_type): Rename into... > (_Rb_tree_const_iterator<>::_Node_ptr): ...this. > (_Rb_tree_const_iterator<>::_M_node): Change type into > _Base_ptr. > (_Rb_tree_const_iterator<>::_M_const_cast): Remove. > (_Rb_tree_helpers<>): New. > (_Rb_tree_piterator): New. > (_Rb_tree_const_piterator): New. > (_Rb_tree_node_traits<>): New. > (_Rb_tree::_Node_base, _Rb_tree::_Node_type): New. > (_Rb_tree<>::_Const_Base_ptr): Remove. > (_Rb_tree): Adapt to generalize usage of _Base_ptr in > place of _Link_type. > (_Rb_tree<>::_M_mbegin): Remove. > (_Rb_tree<>::_S_left(_Const_Base_ptr)): Remove. > (_Rb_tree<>::_S_right(_Const_Base_ptr)): Remove. > (_Rb_tree<>::_S_maximum(_Const_Base_ptr)): Remove. > (_Rb_tree<>::_S_minimum(_Const_Base_ptr)): Remove. > * testsuite/23_containers/map/allocator/ext_ptr.cc: New > test case. > * testsuite/23_containers/multimap/allocator/ext_ptr.cc: > New test case. > * testsuite/23_containers/multiset/allocator/ext_ptr.cc: > New test case. > * testsuite/23_containers/set/allocator/ext_ptr.cc: New > test case. > > Tested under Linux x64. > > > Note that I've also run the 23_containers tests on map, multimap, > multiset and set tweaking implementation > so that new types are being used when C++11 or later even if allocator > pointer type is a C pointer. > > Please take a look at the _GLIBCXX_USE_ALLOC_PTR_xxx macros > inhttps://gcc.gnu.org/pipermail/gcc-patches/2024-November/669128.html > and https://gcc.gnu.org/pipermail/gcc-patches/2024-November/669127.html > Those macros allow the fancy pointer support to be disabled, in case > it will break anything for current users. Maybe more importantly, it > allows the new node types to be used always, even for T*. That means > we can run the entire testsuite using the new nodes without tweaking > the implementation. > > Please consider a _GLIBCXX_USE_ALLOC_PTR_FOR_RB_TREE macro (or some > other name). > > > Ok to commit ? > > François > > On 12/11/2024 15:56, Jonathan Wakely wrote: > > On Mon, 4 Nov 2024 at 21:34, François Dumont <frs.dum...@gmail.com> > <frs.dum...@gmail.com> wrote: > > On 04/11/2024 19:45, Jonathan Wakely wrote: > > On Mon, 4 Nov 2024 at 18:30, François Dumont <frs.dum...@gmail.com> > <frs.dum...@gmail.com> wrote: > > On 21/10/2024 06:56, François Dumont wrote: > > > On 17/10/2024 23:11, Jonathan Wakely wrote: > > > > On Thu, 17 Oct 2024 at 21:39, Jonathan Wakely <jwakely....@gmail.com> > <jwakely....@gmail.com> wrote: > > On Thu, 17 Oct 2024 at 20:52, François Dumont <frs.dum...@gmail.com> > <frs.dum...@gmail.com> wrote: > > Here is an updated version that compiles, I think, all your feedbacks. It's > much cleaner indeed. > > Thanks, I'll take a look tomorrow. > > > It's also tested in C++98/17/23. > > I'm surprised that we do not need to consider potential > allocator::const_pointer. > > Do you mean consider the case where Alloc::const_pointer is not the same type > as rebinding 'pointer' to a const element type? > > Yes, exactly. > > > > We don't need to consider that because we never get a 'const_pointer' from > the allocator, and we never need to pass a 'const_pointer' to the allocator. > The allocator's 'allocate' and 'deallocate' members both work with the > 'pointer' type, so we only need to use that type when interacting with the > allocator. For all the other uses, such as _Const_Node_ptr, what we need is a > pointer-to-const that's compatible with the allocator's pointer type. It > doesn't actually matter if it's the same type as > allocator_traits<Alloc>::const_pointer, because we don't need > > Sorry, I sent the email before finishing that thought! > > ... we don't need to pass a const_pointer to anything, we only need it for > the container's own purposes. > > But thinking about it some more, do we even need a const-pointer for the > container? Currently the const_iterator stores a const-pointer, and some > members like _M_root() and _M_leftmost() return a const-pointer. But they > don't need to. The nodes are all pointed to by a non-const _Base_ptr, none of > the storage managed by the container is const. > > Except _M_impl._M_header in a const context. > > Using const_cast would result in a similar UB that you fixed on > _GLIBCXX_DEBUG containers, no ? > > We only use a pointer to _M_header for the past-the-end iterator, > which is not dereferenceable. It's OK to do the const_cast, it's not > OK to write something through that pointer/reference if the object is > really const. But you can't write through a past-the-end pointer > anyway. > > > One last question then to complete this work, is it fine to change > _Rb_tree_const_iterator::_M_node to _Base_ptr ? No abi issue in doing so ? > > I thought I'd sent a reply to this question, but I don't see it in the > archives, sorry. > > So changing from _Const_Base_ptr to _Base_ptr? Yes, I think that's OK. > > > diff --git a/libstdc++-v3/include/bits/stl_set.h > b/libstdc++-v3/include/bits/stl_set.h > index c0eb4dbf65f..3d17626f330 100644 > --- a/libstdc++-v3/include/bits/stl_set.h > +++ b/libstdc++-v3/include/bits/stl_set.h > @@ -875,7 +875,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER > template<typename _Kt> > auto > upper_bound(const _Kt& __x) const > - -> decltype(iterator(_M_t._M_upper_bound_tr(__x))) > + -> decltype(const_iterator(_M_t._M_upper_bound_tr(__x))) > { return const_iterator(_M_t._M_upper_bound_tr(__x)); } > #endif > ///@} > diff --git a/libstdc++-v3/include/bits/stl_tree.h > b/libstdc++-v3/include/bits/stl_tree.h > index bc27e191e8b..c6cba99726d 100644 > --- a/libstdc++-v3/include/bits/stl_tree.h > +++ b/libstdc++-v3/include/bits/stl_tree.h > @@ -96,44 +96,100 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > > enum _Rb_tree_color { _S_red = false, _S_black = true }; > > - struct _Rb_tree_node_base > - { > - typedef _Rb_tree_node_base* _Base_ptr; > - typedef const _Rb_tree_node_base* _Const_Base_ptr; > - > - _Rb_tree_color _M_color; > - _Base_ptr _M_parent; > - _Base_ptr _M_left; > - _Base_ptr _M_right; > - > - static _Base_ptr > - _S_minimum(_Base_ptr __x) _GLIBCXX_NOEXCEPT > +#if __cplusplus >= 201103L > + template<typename _Ptr> > + struct _Rb_tree_ptr_traits > { > - while (__x->_M_left != 0) __x = __x->_M_left; > - return __x; > - } > + template<typename _Tp> > + struct __rebind > + { using __type = __ptr_rebind<_Ptr, _Tp>; }; > > This only seems to be used in one place, can you just use __ptr_rebind > directly in that one place? > > > + > + template<typename _BasePtr> > + static _Ptr > + _S_ptr_to(_BasePtr __this) noexcept > + { return pointer_traits<_Ptr>::pointer_to(*__this); } > > This is also only needed in exactly one place. I avoided needing this > function by adding _M_base_ptr() to the node base classes. > > The whole of this _Rb_tree_ptr_traits class template seems unnecessary > to me. Just inline the code directly into the places it's needed. > You're not optimizing anything by replacing > pointer_traits<T*>::pointer_to with > _Rb_tree_ptr_traits<T*>::_S_ptr_to, it's still a class template > specialization and a function call. > > > + > + template<typename _BasePtr, typename _UpPtr> > + static _UpPtr > + _S_up_ptr_to(_BasePtr __this) noexcept > > pointer_to is not guaranteed to be noexcept. > > This appears to be a downcast, i.e. a base-to-derived cast, having > "up" in the name is a bit confusing. > > > + { > + using __ptr_traits = pointer_traits<_UpPtr>; > + using __up_type = typename __ptr_traits::element_type; > + auto __up_this = static_cast<__up_type*>(__this); > + return __ptr_traits::pointer_to(*__up_this); > + } > + }; > +#else > + template<typename _Ptr> > + struct _Rb_tree_ptr_traits; > +#endif > > - static _Const_Base_ptr > - _S_minimum(_Const_Base_ptr __x) _GLIBCXX_NOEXCEPT > + template<typename _Tp> > + struct _Rb_tree_ptr_traits<_Tp*> > { > - while (__x->_M_left != 0) __x = __x->_M_left; > - return __x; > - } > + template<typename> > + struct __rebind > + { typedef _Tp* __type; }; > + > + template<typename _BasePtr> > + static _Tp* > + _S_ptr_to(_BasePtr __this) _GLIBCXX_NOEXCEPT > + { return static_cast<_Tp*>(__this); } > + > + template<typename _BasePtr, typename _UpPtr> > + static _UpPtr > + _S_up_ptr_to(_BasePtr __this) _GLIBCXX_NOEXCEPT > + { return static_cast<_UpPtr>(__this); } > + }; > > - static _Base_ptr > - _S_maximum(_Base_ptr __x) _GLIBCXX_NOEXCEPT > + template<typename _BasePtr> > + struct _Rb_tree_pnode_base > { > - while (__x->_M_right != 0) __x = __x->_M_right; > - return __x; > - } > + typedef typename _Rb_tree_ptr_traits<_BasePtr>::template > + __rebind<_Rb_tree_pnode_base>::__type _Base_ptr; > > The names _BasePtr and _Base_ptr are very similar, and confusing. > It looks like sometimes the template argument that is substituted for > _BasePtr is sometimes a derived class (_Rb_tree_node_base) and > sometimes is _ValPtr. It's never a "base" pointer. Please rename it. > > > - static _Const_Base_ptr > - _S_maximum(_Const_Base_ptr __x) _GLIBCXX_NOEXCEPT > - { > - while (__x->_M_right != 0) __x = __x->_M_right; > - return __x; > - } > - }; > + _Base_ptr > + _M_self_ptr() _GLIBCXX_NOEXCEPT > > Why do we need a non-const overload? The const one will work for all > cases, right? > > I added a comment to my list patches pointing out that it's not > const-correct, but is safe as long as only used carefully. > > > > + { return _Rb_tree_ptr_traits<_Base_ptr>::_S_ptr_to(this); } > + > + _Base_ptr > + _M_self_ptr() const _GLIBCXX_NOEXCEPT > + { > + _Rb_tree_pnode_base* __self = const_cast<_Rb_tree_pnode_base*>(this); > + return __self->_M_self_ptr(); > + } > + > + template<typename _NodePtr> > + _NodePtr > + _M_node_ptr() _GLIBCXX_NOEXCEPT > + { > + return _Rb_tree_ptr_traits<_Base_ptr>::template > + _S_up_ptr_to<_Rb_tree_pnode_base*, _NodePtr>(this); > + } > + > + _Rb_tree_color _M_color; > + _Base_ptr _M_parent; > + _Base_ptr _M_left; > + _Base_ptr _M_right; > + > + static _Base_ptr > + _S_minimum(_Base_ptr __x) _GLIBCXX_NOEXCEPT > + { > + while (__x->_M_left) __x = __x->_M_left; > + return __x; > + } > + > + static _Base_ptr > + _S_maximum(_Base_ptr __x) _GLIBCXX_NOEXCEPT > + { > + while (__x->_M_right) __x = __x->_M_right; > + return __x; > + } > + }; > + > + struct _Rb_tree_node_base > + : _Rb_tree_pnode_base<_Rb_tree_node_base*> > + { }; > > This approaches means that the existing specializations of RB trees > all get twice as many base classes. I don't see an ABI problem, and it > should generate the same code for optimized builds, but increasing the > inheritance depth and the number of class template instantiations > gives the compiler more work to do, compiling slower, and generating > additional debug info (and maybe RTTI?) > > Please take a look at how I did it for the linked lists, where > specializations for non-fancy pointers, e.g. _List_node<T>, are mostly > unchanged. They don't get any new base classes, just some new typedefs > and new accessors like: > > _Base_ptr _M_base() { return this; } > const _List_node* _M_base() const { return this; } > > The _List_node_traits class template is used by std::list etc. to > select whether to use _List_node<Alloc::value_type> or > __list::_Node<Alloc::pointer>. All the fancy pointer support is in the > new type hierarchy, __list::_Node_base, __list::_Node, and > __list::_Iterator. The old types like _List_node don't really change. > > I found that using the __list namespace with new types reduced a lot > of confusion I had between the very similar names _List_node and > _List_pnode (or _List_ptr_node, or other names like that). > > Maybe we could call it _List::_Node instead of __list::_Node, so it's > a bit closer to _List_node but still distinguishable (and would > produce very slightly shorter mangled names). I didn't do that, > because our convention is to name impl namespaces as __foo not _Foo. > > That pattern could naturally be extended to __tree::_Node (or > _Tree::_Node) as well. > > > // Helper type offering value initialization guarantee on the compare > functor. > template<typename _Key_compare> > @@ -163,81 +219,108 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > }; > > // Helper type to manage default initialization of node count and header. > - struct _Rb_tree_header > - { > - _Rb_tree_node_base _M_header; > - size_t _M_node_count; // Keeps track of size of tree. > - > - _Rb_tree_header() _GLIBCXX_NOEXCEPT > + template<typename _NodeBase> > + struct _Rb_tree_pheader > { > - _M_header._M_color = _S_red; > - _M_reset(); > - } > + private: > + typedef typename _NodeBase::_Base_ptr _Base_ptr; > + > + public: > + _NodeBase _M_header; > + size_t _M_node_count; // Keeps track of size of tree. > + > + _Rb_tree_pheader() _GLIBCXX_NOEXCEPT > + { > + _M_header._M_color = _S_red; > + _M_reset(); > + } > > #if __cplusplus >= 201103L > - _Rb_tree_header(_Rb_tree_header&& __x) noexcept > - { > - if (__x._M_header._M_parent != nullptr) > - _M_move_data(__x); > - else > - { > - _M_header._M_color = _S_red; > - _M_reset(); > - } > - } > + _Rb_tree_pheader(_Rb_tree_pheader&& __x) noexcept > + { > + if (__x._M_header._M_parent) > + _M_move_data(__x); > + else > + { > + _M_header._M_color = _S_red; > + _M_reset(); > + } > + } > #endif > > - void > - _M_move_data(_Rb_tree_header& __from) > - { > - _M_header._M_color = __from._M_header._M_color; > - _M_header._M_parent = __from._M_header._M_parent; > - _M_header._M_left = __from._M_header._M_left; > - _M_header._M_right = __from._M_header._M_right; > - _M_header._M_parent->_M_parent = &_M_header; > - _M_node_count = __from._M_node_count; > - > - __from._M_reset(); > - } > + void > + _M_move_data(_Rb_tree_pheader& __from) > + { > + _M_header._M_color = __from._M_header._M_color; > + _M_header._M_parent = __from._M_header._M_parent; > + _M_header._M_left = __from._M_header._M_left; > + _M_header._M_right = __from._M_header._M_right; > + _M_header._M_parent->_M_parent = _M_header._M_self_ptr(); > + _M_node_count = __from._M_node_count; > + > + __from._M_reset(); > + } > > - void > - _M_reset() > - { > - _M_header._M_parent = 0; > - _M_header._M_left = &_M_header; > - _M_header._M_right = &_M_header; > - _M_node_count = 0; > - } > - }; > + void > + _M_reset() > + { > + _M_header._M_parent = _Base_ptr(); > + _M_header._M_left = _M_header._M_right = _M_header._M_self_ptr(); > + _M_node_count = 0; > + } > + }; > + > + struct _Rb_tree_header : _Rb_tree_pheader<_Rb_tree_node_base> > + { }; > > template<typename _Val> > - struct _Rb_tree_node : public _Rb_tree_node_base > + struct _Rb_tree_node_val > { > - typedef _Rb_tree_node<_Val>* _Link_type; > - > #if __cplusplus < 201103L > _Val _M_value_field; > > + __attribute__((__always_inline__)) > _Val* > _M_valptr() > { return std::__addressof(_M_value_field); } > > + __attribute__((__always_inline__)) > const _Val* > _M_valptr() const > { return std::__addressof(_M_value_field); } > #else > __gnu_cxx::__aligned_membuf<_Val> _M_storage; > > For the new __list::_Node type I took the opportunity to replace > __aligned_membuf with a union { _Val _M_data; }; which is simpler and > produces the same code. We could probably make that change for the > existing node types, I think it's safe, but making it only for the new > _Node type means the old node type isn't affected. > > > > + [[__gnu__::__always_inline__]] > _Val* > _M_valptr() > { return _M_storage._M_ptr(); } > > + [[__gnu__::__always_inline__]] > const _Val* > _M_valptr() const > { return _M_storage._M_ptr(); } > #endif > }; > > + template<typename _Val> > + struct _Rb_tree_node > + : _Rb_tree_node_base > + , _Rb_tree_node_val<_Val> > + { > + typedef _Rb_tree_node<_Val>* _Node_ptr; > + }; > + > +#if __cplusplus >= 201103L > + template<typename _ValPtr> > + struct _Rb_tree_pnode > + : _Rb_tree_pnode_base<_ValPtr> > > This makes _Rb_tree_pnode_base depend on the value_type, which means > you instantiate a different specialization of _Rb_tree_pnode_base for > every _Rb_tree. There's no point even having a common base class if > every specialization is different. You might as well just put the > _M_parent/_M_left/_M_right pointers in _Rb_tree_pnode directly. > > The point of the node_base class is to have a common base that is not > dependent on the value type, so that std::set<int> and std::set<long> > and std::set<int, Cmp> all use the same node base. The way this patch > works, std::set<int> will use _Rb_tree_pnode_base<int*> andstd::set<long> > will use _Rb_tree_pnode_base<long*>. > > This is why my patch for lists uses __ptr_rebind<_ValPtr, void> as the > template argument to _Node_base, so that they all use > _Node_base<void*>. > > Obviously for a fancy pointer, it will be a different specialization, > so std::list<int, A<int>> uses _Node_base<A::void_pointer>, > butstd::list<long, A<long>> uses the same _Node_base<A::void_pointer>. > > > + , _Rb_tree_node_val<typename std::pointer_traits<_ValPtr>::element_type> > + { > + using _Node_ptr = __ptr_rebind<_ValPtr, _Rb_tree_pnode>; > + }; > +#endif > + > _GLIBCXX_PURE _Rb_tree_node_base* > _Rb_tree_increment(_Rb_tree_node_base* __x) throw (); > > > [...] > > > +#if __cplusplus >= 201103L > + template<typename _ValPtr> > + struct _Rb_tree_piterator > + { > + using __ptr_traits = pointer_traits<_ValPtr>; > + using value_type = typename __ptr_traits::element_type; > + using reference = value_type&; > + using pointer = value_type*; > + > + typedef bidirectional_iterator_tag iterator_category; > + typedef ptrdiff_t difference_type; > + > + typedef _Rb_tree_piterator _Self; > + typedef _Rb_tree_pnode_base<_ValPtr> _Node_base; > + typedef _Rb_tree_pnode<_ValPtr> _Node_type; > + typedef typename _Node_type::_Base_ptr _Base_ptr; > + typedef typename _Node_type::_Node_ptr _Node_ptr; > + typedef _Rb_tree_helpers<_Node_base> _Tree_helpers; > + > + _Rb_tree_piterator() _GLIBCXX_NOEXCEPT > + : _M_node() { } > + > + explicit > + _Rb_tree_piterator(_Base_ptr __x) _GLIBCXX_NOEXCEPT > + : _M_node(__x) { } > + > + reference > + operator*() const _GLIBCXX_NOEXCEPT > + { return *static_cast<_Node_type&>(*_M_node)._M_valptr(); } > + > + pointer > + operator->() const _GLIBCXX_NOEXCEPT > + { return static_cast<_Node_type&>(*_M_node)._M_valptr(); } > + > + _Self& > + operator++() _GLIBCXX_NOEXCEPT > + { > + _M_node = _Tree_helpers::_Increment(_M_node); > + return *this; > + } > + > + _Self > + operator++(int) _GLIBCXX_NOEXCEPT > + { > + _Self __tmp = *this; > + _M_node = _Tree_helpers::_Increment(_M_node); > + return __tmp; > + } > + > + _Self& > + operator--() _GLIBCXX_NOEXCEPT > + { > + _M_node = _Tree_helpers::_Decrement(_M_node); > + return *this; > + } > + > + _Self > + operator--(int) _GLIBCXX_NOEXCEPT > + { > + _Self __tmp = *this; > + _M_node = _Tree_helpers::_Decrement(_M_node); > + return __tmp; > + } > + > + friend bool > + operator==(const _Self& __x, const _Self& __y) _GLIBCXX_NOEXCEPT > + { return __x._M_node == __y._M_node; } > + > +#if ! __cpp_lib_three_way_comparison > + friend bool > + operator!=(const _Self& __x, const _Self& __y) _GLIBCXX_NOEXCEPT > + { return __x._M_node != __y._M_node; } > +#endif > + > + _Base_ptr _M_node; > + }; > + > + template<typename _ValPtr> > + struct _Rb_tree_const_piterator > > For the linked lists, I didn't add two new iterator types, just > _Iterator<_Const, _ValPtr> which is used for iterator and > const_iterator. > > It seemed nice not to duplicate the iterator type, since the iterator > and const_iterator are 95% the same. If we don't need to support > C++98, we can write a single iterator implementation. > > If you have a single iterator implementation, you probably don't need > the _Rb_tree_helpers. > > There would still be uses of insert-and-rebalance and > rebalance-for-erase, but those could just be new overloads for fancy > pointers. I don't think we need the _Rb_tree_helpers class template. > > > > + { > + using __ptr_traits = pointer_traits<_ValPtr>; > + using value_type = typename __ptr_traits::element_type; > + using reference = const value_type&; > + using pointer = const value_type*; > + > + typedef _Rb_tree_piterator<_ValPtr> iterator; > + typedef typename iterator::_Base_ptr _Base_ptr; > + > + typedef bidirectional_iterator_tag iterator_category; > + typedef ptrdiff_t difference_type; > + > + typedef _Rb_tree_const_piterator _Self; > + typedef _Rb_tree_pnode_base<_ValPtr> _Node_base; > + typedef _Rb_tree_pnode<_ValPtr> _Node_type; > + typedef _Rb_tree_helpers<_Node_base> _Tree_helpers; > + > + _Rb_tree_const_piterator() _GLIBCXX_NOEXCEPT > + : _M_node() { } > + > + explicit > + _Rb_tree_const_piterator(_Base_ptr __x) _GLIBCXX_NOEXCEPT > + : _M_node(__x) { } > + > + _Rb_tree_const_piterator(const iterator& __it) _GLIBCXX_NOEXCEPT > + : _M_node(__it._M_node) { } > + > + reference > + operator*() const _GLIBCXX_NOEXCEPT > + { return *static_cast<_Node_type&>(*_M_node)._M_valptr(); } > + > + pointer > + operator->() const _GLIBCXX_NOEXCEPT > + { return static_cast<_Node_type&>(*_M_node)._M_valptr(); } > + > + _Self& > + operator++() _GLIBCXX_NOEXCEPT > + { > + _M_node = _Tree_helpers::_Increment(_M_node); > > You need the iterator increment code here, but you can define it > inline without the _Increment helper. > > > + return *this; > + } > + > + _Self > + operator++(int) _GLIBCXX_NOEXCEPT > + { > + _Self __tmp = *this; > + _M_node = _Tree_helpers::_Increment(_M_node); > > This could just use ++*this. So if there's no separate const iterator > implementation, there's no more use of _Increment. > > > + return __tmp; > + } > + > + _Self& > + operator--() _GLIBCXX_NOEXCEPT > + { > + _M_node = _Tree_helpers::_Decrement(_M_node); > + return *this; > + } > + > + _Self > + operator--(int) _GLIBCXX_NOEXCEPT > + { > + _Self __tmp = *this; > + _M_node = _Tree_helpers::_Decrement(_M_node); > + return __tmp; > + } > + > + friend bool > + operator==(const _Self& __x, const _Self& __y) _GLIBCXX_NOEXCEPT > + { return __x._M_node == __y._M_node; } > + > +#if ! __cpp_lib_three_way_comparison > + friend bool > + operator!=(const _Self& __x, const _Self& __y) _GLIBCXX_NOEXCEPT > + { return __x._M_node != __y._M_node; } > +#endif > + > + _Base_ptr _M_node; > + }; > +#endif // C++11 > + > +#if __cplusplus >= 201103L > + template<typename _ValPtr> > + struct _Rb_tree_node_traits > + { > + using _Node_base = _Rb_tree_pnode_base<_ValPtr>; > + using _Node_type = _Rb_tree_pnode<_ValPtr>; > + using _Header_t = _Rb_tree_pheader<_Node_base>; > + using _Iterator_t = _Rb_tree_piterator<_ValPtr>; > + using _Const_iterator_t = _Rb_tree_const_piterator<_ValPtr>; > + }; > +#else > + template<typename _ValPtr> > + struct _Rb_tree_node_traits; > +#endif > + > + template<typename _Val> > + struct _Rb_tree_node_traits<_Val*> > + { > + typedef _Rb_tree_node_base _Node_base; > + typedef _Rb_tree_node<_Val> _Node_type; > + typedef _Rb_tree_header _Header_t; > + typedef _Rb_tree_iterator<_Val> _Iterator_t; > + typedef _Rb_tree_const_iterator<_Val> _Const_iterator_t; > + }; > > #if __cplusplus > 201402L > template<typename _Tree1, typename _Cmp2> > @@ -424,16 +1068,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > typename _Compare, typename _Alloc = allocator<_Val> > > class _Rb_tree > { > + typedef typename __gnu_cxx::__alloc_traits<_Alloc>::pointer _ValPtr; > + typedef _Rb_tree_node_traits<_ValPtr> _Node_traits; > + > + typedef typename _Node_traits::_Node_base _Node_base; > + typedef typename _Node_traits::_Node_type _Node_type; > + > typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template > - rebind<_Rb_tree_node<_Val> >::other _Node_allocator; > + rebind<_Node_type>::other _Node_allocator; > > typedef __gnu_cxx::__alloc_traits<_Node_allocator> _Alloc_traits; > > protected: > - typedef _Rb_tree_node_base* _Base_ptr; > - typedef const _Rb_tree_node_base* _Const_Base_ptr; > - typedef _Rb_tree_node<_Val>* _Link_type; > - typedef const _Rb_tree_node<_Val>* _Const_Link_type; > + typedef typename _Node_base::_Base_ptr _Base_ptr; > + typedef typename _Node_type::_Node_ptr _Node_ptr; > > Accessing a member of _Node_type here requires _Node_type to be > complete, which requires _Tp to be complete. That's allowed by the > standard, because std::map and std::set don't support incomplete types > (unlike vector and the linked lists). > > But we don't really ned to instantiate _Node_type here, we could use > _Alloc_traits::pointer instead. > > > [...] > > > @@ -1826,10 +2427,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > || _M_impl._M_key_compare(_KeyOfValue()(__v), > _S_key(__p))); > > - _Link_type __z = __node_gen(_GLIBCXX_FORWARD(_Arg, __v)); > + _Base_ptr __z = __node_gen(_GLIBCXX_FORWARD(_Arg, __v)); > > I think this relies on implicit conversion from _Node_ptr to > _Base_ptr, which is not necessarily possible. It works for the > _Pointer_adapter type in <ext/pointer.h> because it has a > template<typename U> _Pointer_adapter(_Pointer_adapter<U>) constructor > that supports implicit conversions. > > There seems to be a few other places with this problem. Please take a > look at the Pointer type in the new test for my list and forward_list > fancy ptr patches (that Pointer should probably be added to the > testsuite/util/testsuite_allocator.h header for reuse). > > I solved these problems by adding a _M_base_ptr() function to the > node_base type, which means that instead of relying on an implicit > conversion from node_ptr to _Base_ptr you call node_ptr->_M_base_ptr() > to get a _Base_ptr. > > > @@ -2255,7 +2821,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > } > else > // Equivalent keys. > - return _Res(__pos._M_node, 0); > + return _Res(__position._M_node, 0); > } > > template<typename _Key, typename _Val, typename _KeyOfValue, > @@ -2294,11 +2860,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>:: > _M_get_insert_hint_equal_pos(const_iterator __position, const key_type& > __k) > { > - iterator __pos = __position._M_const_cast(); > > This removes __pos ... > > > typedef pair<_Base_ptr, _Base_ptr> _Res; > > // end() > - if (__pos._M_node == _M_end()) > + if (__position._M_node == _M_end()) > { > if (size() > 0 > && !_M_impl._M_key_compare(__k, _S_key(_M_rightmost()))) > @@ -2306,18 +2871,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > else > return _M_get_insert_equal_pos(__k); > } > - else if (!_M_impl._M_key_compare(_S_key(__pos._M_node), __k)) > + else if (!_M_impl._M_key_compare(_S_key(__position._M_node), __k)) > { > // First, try before... > iterator __before = __pos; > > But it's still used here. > > > - if (__pos._M_node == _M_leftmost()) // begin() > + if (__position._M_node == _M_leftmost()) // begin() > return _Res(_M_leftmost(), _M_leftmost()); > else if (!_M_impl._M_key_compare(__k, _S_key((--__before)._M_node))) > { > if (_S_right(__before._M_node) == 0) > return _Res(0, __before._M_node); > else > - return _Res(__pos._M_node, __pos._M_node); > + return _Res(__position._M_node, __position._M_node); > } > else > return _M_get_insert_equal_pos(__k); > @@ -2326,12 +2891,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > { > // ... then try after. > iterator __after = __pos; > > And here. > > I think they should both be __position._M_const_cast(). > > > > diff --git a/libstdc++-v3/testsuite/23_containers/map/allocator/ext_ptr.cc > b/libstdc++-v3/testsuite/23_containers/map/allocator/ext_ptr.cc > new file mode 100644 > index 00000000000..758beedccfd > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/map/allocator/ext_ptr.cc > @@ -0,0 +1,44 @@ > +// { dg-do run { target c++11 } } > + > +#include <map> > +#include <memory> > +#include <testsuite_hooks.h> > +#include <testsuite_allocator.h> > + > +struct T { int i; }; > + > +bool operator<(const T& l, const T& r) > +{ return l.i < r.i; } > + > +struct H > > This struct H occurs in several tests but seems to be unused. > > > +{ > + std::size_t > + operator()(const T& t) const noexcept > + { return t.i; } > +}; > + > +struct L : std::less<T> > +{ }; > + > +using __gnu_test::CustomPointerAlloc; > + > +template class std::map<T, int, L, > + CustomPointerAlloc<std::pair<const T, int>>>; > + > +void test01() > +{ > + typedef CustomPointerAlloc<std::pair<const T, int>> alloc_type; > + typedef std::map<T, int, L, alloc_type> test_type; > + test_type v; > + v.insert({ T(), 0 }); > + VERIFY( v.begin()->second == 0 ); > + VERIFY( ++v.begin() == v.end() ); > + v.insert({ T { 1 }, 1 }); > + VERIFY( v.size() == 2 ); > + VERIFY( v.find(T()) != v.end() ); > +} > + > +int main() > +{ > + test01(); > +} > > I'd be interested on your opinion on my patches for fancy pointer > support in the linked lists. I think it's cleaner and requires fewer > changes to the existing specializations. > > My changes can also be disabled by a macro (to avoid breaking any > users who are somehow managing to use allocators-with-fancy-pointers > with RB trees today, which could be useful unless that's completely > broken today), and to be enabled for non-fancy pointers (for test > coverage). > > >