// Copyright (c) 2003,2004,2007-2010 INRIA Sophia-Antipolis (France). // Copyright (c) 2014 GeometryFactory Sarl (France) // All rights reserved. // // This file is part of CGAL (www.cgal.org) // // $URL$ // $Id$ // SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial // // Author(s) : Sylvain Pion #ifndef CGAL_COMPACT_CONTAINER_H #define CGAL_COMPACT_CONTAINER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // An STL like container with the following properties : // - to achieve compactness, it requires access to a pointer stored in T, // specified by a traits. This pointer is supposed to be 4 bytes aligned // when the object is alive, otherwise, the container uses the 2 least // significant bits to store information in the pointer. // - Ts are allocated in arrays of increasing size, which are linked together // by their first and last element. // - the iterator looks at the famous 2 bits to know if it has to deal with // a free/used/boundary element. // TODO : // - Add .resize() (and proper copy of capacity_). // - Add preconditions in input that real pointers need to have clean bits. // Also for the allocated memory alignment, and sizeof(). // - Do a benchmark before/after. // - Check the end result with Valgrind. // - The bit squatting mechanism will be reused for the conflict flag, maybe // it could be put out of the class. // TODO low priority : // - rebind<> the allocator // - Exception safety guarantees // - Thread safety guarantees // - std requirements on iterators says all defined operations are constant // time amortized (it's not true here, maybe it could be with some work...) // - all this is expected especially when there are not so many free objects // compared to the allocated elements. // - Should block_size be selectable/hintable by .reserve() ? // - would be nice to have a temporary_free_list (still active elements, but // which are going to be freed soon). Probably it prevents compactness. // - eventually something to copy this data structure, providing a way to // update the pointers (give access to a hash_map, at least a function that // converts an old pointer to the new one ?). Actually it doesn't have to // be stuck to a particular DS, because for a list it's useful too... // - Currently, end() can be invalidated on insert() if a new block is added. // It would be nice to fix this. We could insert the new block at the // beginning instead ? That would drop the property that iterator order // is preserved. Maybe it's not a problem if end() is not preserved, after // all nothing is going to dereference it, it's just for comparing with // end() that it can be a problem. // Another way would be to have end() point to the end of an always // empty block (containing no usable element), and insert new blocks just // before this one. // Instead of having the blocks linked between them, the start/end pointers // could point back to the container, so that we can do more interesting // things (e.g. freeing empty blocks automatically) ? namespace CGAL { #define CGAL_INIT_COMPACT_CONTAINER_BLOCK_SIZE 14 #define CGAL_INCREMENT_COMPACT_CONTAINER_BLOCK_SIZE 16 template struct Addition_size_policy { static const unsigned int first_block_size = first_block_size_; template static void increase_size(Compact_container& cc) { cc.block_size += block_size_increment; } template static void get_index_and_block(typename Compact_container::size_type i, typename Compact_container::size_type& index, typename Compact_container::size_type& block) { typedef typename Compact_container::size_type ST; const ST TWO_M_N = 2*first_block_size_ - block_size_increment; ST delta = TWO_M_N*TWO_M_N + 8*block_size_increment*i; block= (static_cast(std::sqrt(static_cast(delta))) - TWO_M_N) / (2*block_size_increment); if ( block==0 ) { index = i + 1; } else { const typename Compact_container::size_type first_element_in_block = block*(first_block_size_+ (block_size_increment*(block - 1))/2); index=i - first_element_in_block + 1; } } }; template struct Constant_size_policy { static const unsigned int first_block_size = k; template static void increase_size(Compact_container& /*cc*/) {} template static void get_index_and_block(typename Compact_container::size_type i, typename Compact_container::size_type& index, typename Compact_container::size_type& block) { block=i/k; index=(i%k)+1; } }; // The following base class can be used to easily add a squattable pointer // to a class (maybe you lose a bit of compactness though). // TODO : Shouldn't adding these bits be done automatically and transparently, // based on the traits class info ? class Compact_container_base { void * p; public: Compact_container_base() : p(nullptr) {} void * for_compact_container() const { return p; } void for_compact_container(void* ptr) { p = ptr; } }; // The traits class describes the way to access the pointer. // It can be specialized. template < class T > struct Compact_container_traits { static void * pointer(const T &t) { return t.for_compact_container(); } static void set_pointer(T &t, void* p) { t.for_compact_container(p); } }; namespace internal { template < class DSC, bool Const > class CC_iterator; CGAL_GENERATE_MEMBER_DETECTOR(increment_erase_counter); // A basic "no erase counter" strategy template class Erase_counter_strategy { public: // Do nothing template static unsigned int erase_counter(const Element &) { return 0; } template static void set_erase_counter(Element &, unsigned int) {} template static void restore_erase_counter(Element*, unsigned int) {} template static void increment_erase_counter(Element &) {} }; // A strategy managing an internal counter template <> class Erase_counter_strategy { public: template static unsigned int erase_counter(const Element &e) { return e.erase_counter(); } template static unsigned int erase_counter(Element* e) { return e->erase_counter(); } template static void set_erase_counter(Element &e, unsigned int c) { e.set_erase_counter(c); } template static void restore_erase_counter(Element* e, unsigned int c) { e->set_erase_counter(c); } template static void increment_erase_counter(Element &e) { e.increment_erase_counter(); } }; template struct Time_stamp_and_erase_counter_backup_and_restore_guard { T* const ptr; const unsigned int ec; std::size_t ts; using EraseCounterStrategy = internal::Erase_counter_strategy::value>; Time_stamp_and_erase_counter_backup_and_restore_guard(T* ptr) : ptr(ptr), ec(EraseCounterStrategy::erase_counter(ptr)) { if constexpr (Time_stamper::has_timestamp) { ts = Time_stamper::time_stamp(ptr); } } ~Time_stamp_and_erase_counter_backup_and_restore_guard() { EraseCounterStrategy::restore_erase_counter(ptr, ec); if constexpr (Time_stamper::has_timestamp) { Time_stamper::restore_timestamp(ptr, ts); } } }; } template < class T, class Allocator_ = Default, class Increment_policy_ = Default, class TimeStamper_ = Default > class Compact_container { typedef Allocator_ Al; typedef typename Default::Get< Al, CGAL_ALLOCATOR(T) >::type Allocator; typedef Increment_policy_ Ip; typedef typename Default::Get< Ip, Addition_size_policy >::type Increment_policy; typedef TimeStamper_ Ts; template using EraseCounterStrategy = internal::Erase_counter_strategy::value>; typedef Compact_container Self; typedef Compact_container_traits Traits; public: typedef typename Default::Get< TimeStamper_, CGAL::Time_stamper_impl >::type Time_stamper; typedef Time_stamper Time_stamper_impl; // backward-compatibility typedef T value_type; typedef Allocator allocator_type; typedef value_type& reference; typedef const value_type& const_reference; typedef typename std::allocator_traits::pointer pointer; typedef typename std::allocator_traits::const_pointer const_pointer; typedef typename std::allocator_traits::size_type size_type; typedef typename std::allocator_traits::difference_type difference_type; typedef internal::CC_iterator iterator; typedef internal::CC_iterator const_iterator; typedef std::reverse_iterator reverse_iterator; typedef std::reverse_iterator const_reverse_iterator; friend class internal::CC_iterator; friend class internal::CC_iterator; template friend struct Addition_size_policy; template friend struct Constant_size_policy; explicit Compact_container(const Allocator &a = Allocator()) : alloc(a) { init(); } template < class InputIterator > Compact_container(InputIterator first, InputIterator last, const Allocator & a = Allocator()) : alloc(a) { init(); std::copy(first, last, CGAL::inserter(*this)); } // The copy constructor and assignment operator preserve the iterator order Compact_container(const Compact_container &c) : alloc(c.get_allocator()) { init(); block_size = c.block_size; time_stamp = 0; std::copy(c.begin(), c.end(), CGAL::inserter(*this)); } Compact_container(Compact_container&& c) noexcept : alloc(c.get_allocator()) { c.swap(*this); } Compact_container & operator=(const Compact_container &c) { if (&c != this) { Self tmp(c); swap(tmp); } return *this; } Compact_container & operator=(Compact_container&& c) noexcept { Self tmp(std::move(c)); tmp.swap(*this); return *this; } ~Compact_container() { clear(); } bool check_timestamps_are_valid() const { if constexpr (Time_stamper::has_timestamp) { for(size_type i = 0, end = capacity(); i < end; ++i) { if(!is_used(i)) { continue; } if(Time_stamper::time_stamp(&operator[](i)) != i) { return false; } } } return true; } bool is_used(const_iterator ptr) const { return (type(&*ptr)==USED); } bool is_used(size_type i) const { typename Self::size_type block_number, index_in_block; Increment_policy::template get_index_and_block(i, index_in_block, block_number); return (type(&all_items[block_number].first[index_in_block]) == USED); } const T& operator[] (size_type i) const { CGAL_assertion( is_used(i) ); typename Self::size_type block_number, index_in_block; Increment_policy::template get_index_and_block(i, index_in_block, block_number); return all_items[block_number].first[index_in_block]; } T& operator[] (size_type i) { CGAL_assertion( is_used(i) ); typename Self::size_type block_number, index_in_block; Increment_policy::template get_index_and_block(i, index_in_block, block_number); return all_items[block_number].first[index_in_block]; } friend void swap(Compact_container& a, Compact_container b) noexcept { a.swap(b); } iterator begin() { return empty()?end():iterator(first_item, 0, 0); } iterator end() { return iterator(last_item, 0); } const_iterator begin() const { return empty()?end():const_iterator(first_item, 0, 0); } const_iterator end() const { return const_iterator(last_item, 0); } reverse_iterator rbegin() { return reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } // Boost.Intrusive interface iterator iterator_to(reference value) const { return iterator(&value, 0); } const_iterator iterator_to(const_reference value) const { return const_iterator(&value, 0); } static iterator s_iterator_to(reference value) { return iterator(&value, 0); } static const_iterator s_iterator_to(const_reference value) { return const_iterator(&value, 0); } // Special insert methods that construct the objects in place // (just forward the arguments to the constructor, to optimize a copy). template < typename... Args > iterator emplace(Args&&... args) { if (free_list == nullptr) allocate_new_block(); pointer ret = free_list; free_list = clean_pointee(ret); { internal::Time_stamp_and_erase_counter_backup_and_restore_guard guard(ret); new (ret) value_type(std::forward(args)...); } // this scope is important: the destructor of the guard has to be called // before the time_stamp is set, otherwise it will be set to the wrong value. Time_stamper::set_time_stamp(ret, time_stamp); CGAL_assertion(type(ret) == USED); ++size_; return iterator(ret, 0); } template iterator insert(U&&u) { return emplace(std::forward(u)); } template < class InputIterator > void insert(InputIterator first, InputIterator last) { for (; first != last; ++first) insert(*first); } template < class InputIterator > void assign(InputIterator first, InputIterator last) { clear(); // erase(begin(), end()); // ? insert(first, last); } void erase(iterator x) { auto ptr = &*x; CGAL_precondition(type(ptr) == USED); EraseCounterStrategy::increment_erase_counter(*x); { internal::Time_stamp_and_erase_counter_backup_and_restore_guard guard(ptr); std::allocator_traits::destroy(alloc, ptr); } put_on_free_list(ptr); --size_; } void erase(iterator first, iterator last) { while (first != last) erase(first++); } void clear(); // Merge the content of d into *this. d gets cleared. // The complexity is O(size(free list = capacity-size)). void merge(Self &d); size_type size() const { CGAL_expensive_assertion(size_ == (size_type) std::distance(begin(), end())); return size_; } size_type max_size() const { return std::allocator_traits::max_size(alloc); } size_type capacity() const { return capacity_; } // void resize(size_type sz, T c = T()); // TODO makes sense ??? bool empty() const { return size_ == 0; } allocator_type get_allocator() const { return alloc; } // Returns the index of the iterator "cit", i.e. the number n so that // operator[](n)==*cit. // Complexity : O(#blocks) = O(sqrt(capacity())). // This function is mostly useful for purposes of efficient debugging at // higher levels. size_type index(const_iterator cit) const { // We use the block structure to provide an efficient version : // we check if the address is in the range of each block. CGAL_assertion(cit != end()); const_pointer c = &*cit; size_type res=0; for (typename All_items::const_iterator it = all_items.begin(), itend = all_items.end(); it != itend; ++it) { const_pointer p = it->first; size_type s = it->second; // Are we in the address range of this block (excluding first and last // elements) ? if ( p(-1); // cit does not belong to this compact container } // Returns whether the iterator "cit" is in the range [begin(), end()]. // Complexity : O(#blocks) = O(sqrt(capacity())). // This function is mostly useful for purposes of efficient debugging at // higher levels. bool owns(const_iterator cit) const { // We use the block structure to provide an efficient version : // we check if the address is in the range of each block, // and then test whether it is valid (not a free element). if (cit == end()) return true; const_pointer ptr = &*cit; for (const auto& [chunk_ptr, size] : all_items) { // Are we in the address range of this block (excluding first and last // elements) ? if (ptr <= chunk_ptr || (chunk_ptr+size-1) <= ptr) continue; CGAL_assertion_msg( (ptr-chunk_ptr)+chunk_ptr == ptr, "wrong alignment of iterator"); return type(ptr) == USED; } return false; } bool owns_dereferenceable(const_iterator cit) const { return cit != end() && owns(cit); } // wrong spelling, kept for backward compatibility // cspell:disable-next-line CGAL_DEPRECATED bool owns_dereferencable(const_iterator cit) const { return owns_dereferenceable(cit); } /** Reserve method to ensure that the capacity of the Compact_container be * greater or equal than a given value n. */ void reserve(size_type n) { if ( capacity_>=n ) return; size_type lastblock = all_items.size(); while ( capacity_lastblock ); } private: std::pair push_back_new_block(); void put_block_on_free_list(pointer new_block, size_type block_size); void allocate_new_block(); void put_on_free_list(pointer x) { set_type(x, free_list, FREE); free_list = x; } // Definition of the bit squatting : // ================================= // ptr is composed of a pointer part and the last 2 bits. // Here is the meaning of each of the 8 cases. // // value of the last 2 bits as "Type" // pointer part 0 1 2 3 // nullptr user elt unused free_list end start/end // != nullptr user elt block boundary free elt unused // // meaning of ptr : user stuff next/prev block free_list unused enum Type { USED = 0, BLOCK_BOUNDARY = 1, FREE = 2, START_END = 3 }; // The bit squatting is implemented by casting pointers to (char *), then // subtracting to nullptr, doing bit manipulations on the resulting integer, // and converting back. static char * clean_pointer(char * p) { auto ptr = reinterpret_cast(p); auto mask = static_cast(START_END); return reinterpret_cast(ptr & ~mask); } // Returns the pointee, cleaned up from the squatted bits. static pointer clean_pointee(const_pointer ptr) { void* raw_ptr = Traits::pointer(*ptr); char* cleaned_ptr = clean_pointer(reinterpret_cast(raw_ptr)); return reinterpret_cast(cleaned_ptr); } // Get the type of the pointee. static Type type(const_pointer ptr) { char* p = reinterpret_cast(Traits::pointer(*ptr)); // Compute the difference between the pointer and its cleaned version to extract the last 2 bits. std::ptrdiff_t diff = reinterpret_cast(p) - reinterpret_cast(clean_pointer(p)); return static_cast(diff); } // Sets the pointer part and the type of the pointee. static void set_type(pointer ptr, void * p, Type type) { // This out of range compare is always true and causes lots of // unnecessary warnings. // CGAL_precondition(0 <= t && t < 4); // Clean the pointer to remove any existing type bits, then set the new type bits. char* p_cleaned = clean_pointer(reinterpret_cast(p)); std::ptrdiff_t p_value = reinterpret_cast(p_cleaned); std::ptrdiff_t new_ptr = p_value + static_cast(type); Traits::set_pointer(*ptr, reinterpret_cast(new_ptr)); } public: // @return true iff pts is on the beginning or on the end of its block. static bool is_begin_or_end(const_pointer ptr) { return type(ptr)==START_END; } void swap(Self &c) noexcept { std::swap(alloc, c.alloc); std::swap(capacity_, c.capacity_); std::swap(size_, c.size_); std::swap(block_size, c.block_size); std::swap(first_item, c.first_item); std::swap(last_item, c.last_item); std::swap(free_list, c.free_list); all_items.swap(c.all_items); // non-atomic swap of time_stamp: c.time_stamp = time_stamp.exchange(c.time_stamp.load()); } private: // We store a vector of pointers to all allocated blocks and their sizes. // Knowing all pointers, we don't have to walk to the end of a block to reach // the pointer to the next block. // Knowing the sizes allows to deallocate() without having to compute the size // by walking through the block till its end. // This opens up the possibility for the compiler to optimize the clear() // function considerably when has_trivial_destructor. using All_items = std::vector >; using time_stamp_t = std::atomic; void init() { block_size = Increment_policy::first_block_size; capacity_ = 0; size_ = 0; free_list = nullptr; first_item = nullptr; last_item = nullptr; all_items = All_items(); time_stamp = 0; } allocator_type alloc; size_type capacity_ = 0; size_type size_ = 0; size_type block_size = Increment_policy::first_block_size; pointer free_list = nullptr; pointer first_item = nullptr; pointer last_item = nullptr; All_items all_items = {}; time_stamp_t time_stamp = {}; }; template < class T, class Allocator, class Increment_policy, class TimeStamper > void Compact_container::merge(Self &d) { CGAL_precondition(&d != this); // Allocators must be "compatible" : CGAL_precondition(get_allocator() == d.get_allocator()); // Concatenate the free_lists. if (free_list == nullptr) { free_list = d.free_list; } else if (d.free_list != nullptr) { pointer p = free_list; while (clean_pointee(p) != nullptr) p = clean_pointee(p); set_type(p, d.free_list, FREE); } // Concatenate the blocks. if (last_item == nullptr) { // empty... first_item = d.first_item; last_item = d.last_item; } else if (d.last_item != nullptr) { set_type(last_item, d.first_item, BLOCK_BOUNDARY); set_type(d.first_item, last_item, BLOCK_BOUNDARY); last_item = d.last_item; } all_items.insert(all_items.end(), d.all_items.begin(), d.all_items.end()); // Add the sizes. size_ += d.size_; // Add the capacities. capacity_ += d.capacity_; // It seems reasonable to take the max of the block sizes. block_size = (std::max)(block_size, d.block_size); // Clear d. d.init(); } template < class T, class Allocator, class Increment_policy, class TimeStamper > void Compact_container::clear() { for (typename All_items::iterator it = all_items.begin(), itend = all_items.end(); it != itend; ++it) { pointer p = it->first; size_type s = it->second; for (pointer pp = p + 1; pp != p + s - 1; ++pp) { if (type(pp) == USED) { std::allocator_traits::destroy(alloc, pp); set_type(pp, nullptr, FREE); } } alloc.deallocate(p, s); } init(); } template < class T, class Allocator, class Increment_policy, class TimeStamper > auto Compact_container::push_back_new_block() -> std::pair { pointer new_block = alloc.allocate(block_size + 2); std::pair result{new_block, block_size}; all_items.push_back(std::make_pair(new_block, block_size + 2)); capacity_ += block_size; // We insert this new block at the end. if (last_item == nullptr) // First time { first_item = new_block; last_item = new_block + block_size + 1; set_type(first_item, nullptr, START_END); } else { set_type(last_item, new_block, BLOCK_BOUNDARY); set_type(new_block, last_item, BLOCK_BOUNDARY); last_item = new_block + block_size + 1; } set_type(last_item, nullptr, START_END); // Increase the block_size for the next time. Increment_policy::increase_size(*this); return result; } template < class T, class Allocator, class Increment_policy, class TimeStamper > void Compact_container:: put_block_on_free_list(pointer new_block, size_type block_size) { // The block actually has a size==block_size+2. // We don't touch the first and the last one. // We mark them free in reverse order, so that the insertion order // will correspond to the iterator order... for (size_type i = block_size; i >= 1; --i) { EraseCounterStrategy::set_erase_counter(*(new_block + i), 0); Time_stamper::initialize_time_stamp(new_block + i); put_on_free_list(new_block + i); } } template < class T, class Allocator, class Increment_policy, class TimeStamper > void Compact_container::allocate_new_block() { auto [new_block, block_size] = push_back_new_block(); put_block_on_free_list(new_block, block_size); } template < class T, class Allocator, class Increment_policy, class TimeStamper > inline bool operator==(const Compact_container &lhs, const Compact_container &rhs) { return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); } template < class T, class Allocator, class Increment_policy, class TimeStamper > inline bool operator!=(const Compact_container &lhs, const Compact_container &rhs) { return ! (lhs == rhs); } template < class T, class Allocator, class Increment_policy, class TimeStamper > inline bool operator< (const Compact_container &lhs, const Compact_container &rhs) { return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); } template < class T, class Allocator, class Increment_policy, class TimeStamper > inline bool operator> (const Compact_container &lhs, const Compact_container &rhs) { return rhs < lhs; } template < class T, class Allocator, class Increment_policy, class TimeStamper > inline bool operator<=(const Compact_container &lhs, const Compact_container &rhs) { return ! (lhs > rhs); } template < class T, class Allocator, class Increment_policy, class TimeStamper > inline bool operator>=(const Compact_container &lhs, const Compact_container &rhs) { return ! (lhs < rhs); } // forward-declare Concurrent_compact_container, for CC_iterator template < class T, class Allocator_ > class Concurrent_compact_container; namespace internal { template < class DSC, bool Const > class CC_iterator { typedef CC_iterator Self; public: typedef DSC CC; typedef typename DSC::value_type value_type; typedef typename DSC::size_type size_type; typedef typename DSC::difference_type difference_type; typedef std::conditional_t< Const, const value_type*, value_type*> pointer; typedef std::conditional_t< Const, const value_type&, value_type&> reference; typedef std::bidirectional_iterator_tag iterator_category; // the initialization with nullptr is required by our Handle concept. CC_iterator() #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP : ts(0) #endif { m_ptr = nullptr; } explicit CC_iterator(pointer ptr) : m_ptr(ptr) { } // Converting constructor from mutable to constant iterator template CC_iterator(const CC_iterator< std::enable_if_t<(!OtherConst && Const), DSC>, OtherConst> &const_it) #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP : ts(Time_stamper::time_stamp(const_it.operator->())) #endif { m_ptr = const_it.operator->(); } // Assignment operator from mutable to constant iterator template CC_iterator & operator= (const CC_iterator< std::enable_if_t<(!OtherConst && Const), DSC>, OtherConst> &const_it) { m_ptr = const_it.operator->(); #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP ts = Time_stamper::time_stamp(const_it.operator->()); #endif return *this; } // Construction from nullptr CC_iterator (std::nullptr_t /*CGAL_assertion_code(n)*/) #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP : ts(0) #endif { //CGAL_assertion (n == nullptr); m_ptr = nullptr; } private: typedef typename DSC::Time_stamper Time_stamper; #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP std::size_t ts; #endif pointer m_ptr; // Only Compact_container and Concurrent_compact_container should // access these constructors. template friend class CGAL::Compact_container; friend class CGAL::Concurrent_compact_container; // For begin() CC_iterator(pointer ptr, int, int) #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP : ts(0) #endif { m_ptr = ptr; if (m_ptr == nullptr) // empty container. return; ++(m_ptr); // if not empty, p = start if (DSC::type(m_ptr) == DSC::FREE) increment(); #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP else ts = Time_stamper::time_stamp(m_ptr); #endif // CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP } // Construction from raw pointer and for end(). CC_iterator(pointer ptr, int) #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP : ts(0) #endif { m_ptr = ptr; #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP if(ptr != nullptr){ ts = Time_stamper::time_stamp(m_ptr); } #endif // end CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP } // NB : in case empty container, begin == end == nullptr. void increment() { // It's either pointing to end(), or valid. CGAL_assertion_msg(m_ptr != nullptr, "Incrementing a singular iterator or an empty container iterator ?"); CGAL_assertion_msg(DSC::type(m_ptr) != DSC::START_END, "Incrementing end() ?"); // If it's not end(), then it's valid, we can do ++. do { ++(m_ptr); if (DSC::type(m_ptr) == DSC::USED || DSC::type(m_ptr) == DSC::START_END) { #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP ts = Time_stamper::time_stamp(m_ptr); #endif return; } if (DSC::type(m_ptr) == DSC::BLOCK_BOUNDARY) m_ptr = DSC::clean_pointee(m_ptr); } while (true); } void decrement() { // It's either pointing to end(), or valid. CGAL_assertion_msg(m_ptr != nullptr, "Decrementing a singular iterator or an empty container iterator ?"); CGAL_assertion_msg(DSC::type(m_ptr - 1) != DSC::START_END, "Decrementing begin() ?"); // If it's not begin(), then it's valid, we can do --. do { --m_ptr; if (DSC::type(m_ptr) == DSC::USED || DSC::type(m_ptr) == DSC::START_END) { #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP ts = Time_stamper::time_stamp(m_ptr); #endif return; } if (DSC::type(m_ptr) == DSC::BLOCK_BOUNDARY) m_ptr = DSC::clean_pointee(m_ptr); } while (true); } public: Self & operator++() { CGAL_assertion_msg(m_ptr != nullptr, "Incrementing a singular iterator or an empty container iterator ?"); /* CGAL_assertion_msg(DSC::type(m_ptr) == DSC::USED, "Incrementing an invalid iterator."); */ increment(); return *this; } Self & operator--() { CGAL_assertion_msg(m_ptr != nullptr, "Decrementing a singular iterator or an empty container iterator ?"); /*CGAL_assertion_msg(DSC::type(m_ptr) == DSC::USED || DSC::type(m_ptr) == DSC::START_END, "Decrementing an invalid iterator.");*/ decrement(); return *this; } Self operator++(int) { Self tmp(*this); ++(*this); return tmp; } Self operator--(int) { Self tmp(*this); --(*this); return tmp; } #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP bool is_time_stamp_valid() const { return (ts == 0) || (ts == Time_stamper::time_stamp(m_ptr)); } #endif // CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP reference operator*() const { return *(m_ptr); } pointer operator->() const { return (m_ptr); } // For std::less... bool operator<(const CC_iterator& other) const { #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP CGAL_assertion( is_time_stamp_valid() ); #endif return Time_stamper::less(m_ptr, other.m_ptr); } bool operator>(const CC_iterator& other) const { #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP CGAL_assertion( is_time_stamp_valid() ); #endif return Time_stamper::less(other.m_ptr, m_ptr); } bool operator<=(const CC_iterator& other) const { #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP CGAL_assertion( is_time_stamp_valid() ); #endif return Time_stamper::less(m_ptr, other.m_ptr) || (*this == other); } bool operator>=(const CC_iterator& other) const { #ifdef CGAL_COMPACT_CONTAINER_DEBUG_TIME_STAMP CGAL_assertion( is_time_stamp_valid() ); #endif return Time_stamper::less(other.m_ptr, m_ptr) || (*this == other); } // Can itself be used for bit-squatting. void * for_compact_container() const { return m_ptr; } void for_compact_container(void* p) { m_ptr = static_cast(p); } }; template < class DSC, bool Const1, bool Const2 > inline bool operator==(const CC_iterator &rhs, const CC_iterator &lhs) { return rhs.operator->() == lhs.operator->(); } template < class DSC, bool Const1, bool Const2 > inline bool operator!=(const CC_iterator &rhs, const CC_iterator &lhs) { return rhs.operator->() != lhs.operator->(); } // Comparisons with nullptr are part of CGAL's Handle concept... template < class DSC, bool Const > inline bool operator==(const CC_iterator &rhs, std::nullptr_t /*CGAL_assertion_code(n)*/) { //CGAL_assertion( n == nullptr); return rhs.operator->() == nullptr; } template < class DSC, bool Const > inline bool operator!=(const CC_iterator &rhs, std::nullptr_t /*CGAL_assertion_code(n)*/) { //CGAL_assertion( n == nullptr); return rhs.operator->() != nullptr; } template std::size_t hash_value(const CC_iterator& i) { typedef Time_stamper_impl Stamper; return Stamper::hash_value(i.operator->()); } namespace handle { // supply a specialization for Hash_functor // forward declare base template template struct Hash_functor; template struct Hash_functor >{ std::size_t operator()(const CC_iterator& i) { return hash_value(i); } }; } // namespace handle } // namespace internal template class Output_rep > { protected: using CC_iterator = CGAL::internal::CC_iterator; using Compact_container = typename CC_iterator::CC; using Time_stamper = typename Compact_container::Time_stamper; CC_iterator it; public: Output_rep( const CC_iterator it) : it(it) {} std::ostream& operator()( std::ostream& out) const { return (out << Time_stamper::display_id(it.operator->())); } }; struct With_offset_tag { int offset = 0; }; struct With_point_tag : public With_offset_tag { }; struct With_point_and_info_tag : public With_point_tag {}; template struct Output_rep, With_offset_tag> : public Output_rep> { int offset = 0; using CC_iterator = CGAL::internal::CC_iterator; using Compact_container = typename CC_iterator::CC; using Time_stamper = typename Compact_container::Time_stamper; using Base = Output_rep; using Base::Base; Output_rep(const CC_iterator it, With_offset_tag tag = {}) : Base(it), offset(tag.offset) {} std::ostream& operator()(std::ostream& out) const { out << Time_stamper::display_id(this->it.operator->(), offset); return out; } }; template struct Output_rep, With_point_tag> : public Output_rep, With_offset_tag> { using CC_iterator = CGAL::internal::CC_iterator; using Base = Output_rep; using Time_stamper = typename Base::Time_stamper; using Base::Base; Output_rep(const CC_iterator it, With_point_tag tag = {}) : Base(it, tag) {} std::ostream& operator()(std::ostream& out) const { this->Base::operator()(out); if(this->it.operator->() != nullptr) { if(Time_stamper::time_stamp(this->it.operator->()) == 0) return out << "= infinite_vertex()"; return out << "= " << this->it->point(); } else return out; } }; } //namespace CGAL namespace std { #ifndef CGAL_CFG_NO_STD_HASH template < class DSC, bool Const > struct hash > : public CGAL::cpp98::unary_function, std::size_t> { std::size_t operator()(const CGAL::internal::CC_iterator& i) const { return hash_value(i); } }; #endif // CGAL_CFG_NO_STD_HASH } // namespace std #include #endif // CGAL_COMPACT_CONTAINER_H