On Tue, 8 Oct 2024 at 22:50, Jonathan Wakely <jwakely....@gmail.com> wrote:
> > > On Thu, 1 Aug 2024 at 18:28, François Dumont <frs.dum...@gmail.com> wrote: > >> Hi >> >> Here is a proposal to add fancy pointer support in std::_Rb_tree >> container. >> >> As you'll see there are still several usages of >> pointer_traits<>::pointer_to. The ones in _M_header_ptr() are >> unavoidable. > > > Yes, those are necessary. > > The pointer_to use in _M_const_cast could be simplified by adding a > _M_self_ptr() member to _Rb_tree_pnode_base: > > _Base_ptr > _M_self_ptr() _GLIBCXX_NOEXCEPT > { return pointer_traits<_Base_ptr>::pointer_to(*this); } > I don't think it's needed (because _M_const_cast is only in C++11 code, I think?) but you could define that for both C++11 and C++98: #if __cplusplus _Base_ptr _M_self_ptr() noexcept { return pointer_traits<_Base_ptr>::pointer_to(*this); } #else _Base_ptr _M_self_ptr() { return this; } #endif Then this works for both (if you replace the 'auto'): > _Base_ptr > _M_self_ptr_nc() const _GLIBCXX_NOEXCEPT > { > auto __self = const_cast<_Rb_tree_pnode_base*>(this); > return __self->_M_self_ptr(); > } > > _Const_Base_ptr > _M_self_ptr() const _GLIBCXX_NOEXCEPT > { return pointer_traits<_Const_Base_ptr>::pointer_to(*this); } > > Then _M_const_cast would do: > > return iterator(_M_node->_M_self_ptr_nc()); > > > >> The ones to extract a node or to return a node to the >> allocator are more questionable. Are they fine ? Is there another way to >> mimic the static_cast<_Link_type> that can be done on raw pointers with >> fancy pointers ? >> > > I think you can just do static_cast<_Link_type>(_M_node->_M_self_ptr()) > i.e. convert the _Base_ptr to the derived _Link_type > (we should really rename _Link_type to _Node_ptr, I keep getting confused > by the fact that the actual links between nodes are _Base_ptr, so what does > _Link_type mean now?!) > > Alternatively, you could add a _M_node_ptr() to the _Rb_tree_pnode_base > type: > > template<typename _NodeT> > __ptr_rebind<_BasePtr, _NodeT> > _M_node_ptr() _GLIBCXX_NOEXCEPT > { > auto __node = static_cast<_NodeT*>(this); > using _Node_ptr = __ptr_rebind<_BasePtr, _NodeT>; > return pointer_traits<_Node_ptr>::pointer_to(*__node); > } > You could add a C++98 version of this as well: #if __cplusplus >= 201103L // as above #else template<typename _NodeT> _NodeT* _M_node_ptr() _GLIBCXX_NOEXCEPT { return static_cast<_Node*>(this); } #endif Then the code to deallocate the node would not need to use #if: _Link_type __np = __p->template _M_node_ptr<_Node_type>(); _Alloc_traits::deallocate(_M_get_Node_allocator(), __np, 1); > Then: > auto __np = __p->template _M_node_ptr<_Node_type>() > _Alloc_traits::deallocate(_M_get_Node_allocator(), __np, 1); > > > Some more general comments: > > _Rb_tree_pnode_base and the iterators could just take one template > argument, and use __ptr_rebind to get the const pointer from the non-const > pointer. We can add a helper to do that: > > template<typename _Ptr> struct __add_const_to_ptr; > #if __cplusplus >= 201103L > template<typename _Ptr> > using __add_const_to_ptr = __ptr_rebind<_Ptr, const typename > pointer_traits<_Ptr>::element_type>; > #else > template<typename _Ptr> struct __add_const_to_ptr; > template<typename _Tp> > struct __add_const_to_ptr<_Tp*> > { typedef const _Tp* __type; }; > #endif > > This will let you get the FooCPtr type from the FooPtr type. > > A traits class could replace all the __conditional_t uses: > > #if __cplusplus >= 201103L > template<typename _ValPtr> > struct _Rb_tree_node_traits > { > using _Base = _Rb_tree_pnode_base<_ValPtr>; > using _Node_type = _Rb_tree_pnode<_ValPtr>; > using _Base_ptr = __ptr_rebind<_ValPtr, _Base>; > using _Node_ptr = __ptr_rebind<_ValPtr, _Node>; > using iterator = _Rb_tree_piterator<_ValPtr>; > using const_iterator = _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 _Base; > typedef _Rb_tree_node<_Val> _Node_type; > typedef _Base* _Base_ptr; > typedef _Node* _Node_ptr; > typedef _Rb_tree_iterator<_Val> iterator; > typedef _Rb_tree_const_iterator<_Val> const_iterator; > }; > > I think this would get rid of a lot of the #if checks and the > __conditional_t uses. > > Otherwise this looks really good, nice work! > > >> >> A solution to this (potential) issue would be to: >> >> 1. Store _Node_type pointer in the data structure rather than _Node_base >> pointer. But then _Rb_tree_pheader _M_left/_M_right could not be >> initialized with a pointer to _M_parent which is only a _Node_base >> instance. We would then need to init it to nullptr but I haven't check >> impact on code and more importantly it would be an abi breaking change. >> >> 2. Still store _Node_type pointer in the data structure but then make >> _M_header a _Node_type instance. In this case memory area of the >> value_type would just be lost bytes. >> >> >> libstdc++: Add fancy pointer support in std::[multi][map/set] >> > > Let's just say "maps and sets" or "RB trees" instead of the more cryptic > std::[multi][map/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. >> >> libstdc++-v3/ChangeLog: >> >> * include/bits/stl_tree.h >> (_Rb_tree_pnode_base<>): New. >> (_Rb_tree_node_base): Inherit from latter. >> (_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_helpers): New. >> (_Rb_tree_piterator): New. >> (_Rb_tree_const_piterator): New. >> (_Rb_tree::_Node_base, _Rb_tree::_Node_type): New. >> (_Rb_tree): Adapt to generalize usage of _Base_ptr in place >> of _Link_type. >> * 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. >> >> François >> >