diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000000000..2f9ca8a068282 --- /dev/null +++ b/src/list.h @@ -0,0 +1,2476 @@ +// This software is a modified version of the original. +// The original license is as follows: + +// Copyright (c) 2019, Matthew Bentley (mattreecebentley@gmail.com) www.plflib.org + +// zLib license (https://www.zlib.net/zlib_license.html): +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. + +#pragma once +#ifndef LIST_H +#define LIST_H + +#define LIST_BLOCK_MIN static_cast((sizeof(node) * 8 > (sizeof(*this) + sizeof(group)) * 2) ? 8 : (((sizeof(*this) + sizeof(group)) * 2) / sizeof(node)) + 1) +#define LIST_BLOCK_MAX 2048 + +#define LIST_CONSTEXPR +#define LIST_NOEXCEPT_SWAP(the_allocator) noexcept +#define LIST_NOEXCEPT_MOVE_ASSIGNMENT(the_allocator) noexcept + +// TODO: Switch to these when we move to C++17 +// #define LIST_CONSTEXPR constexpr +// #define LIST_NOEXCEPT_SWAP(the_allocator) noexcept(std::allocator_traits::propagate_on_container_swap::value) +// #define LIST_NOEXCEPT_MOVE_ASSIGNMENT(the_allocator) noexcept(std::allocator_traits::is_always_equal::value) + +// Note: GCC creates faster code without forcing inline +#if defined(_MSC_VER) +#define LIST_FORCE_INLINE __forceinline +#else +#define LIST_FORCE_INLINE +#endif + +// TODO: get rid of these defines +#define LIST_CONSTRUCT(the_allocator, allocator_instance, location, ...) std::allocator_traits::construct(allocator_instance, location, __VA_ARGS__) +#define LIST_DESTROY(the_allocator, allocator_instance, location) std::allocator_traits::destroy(allocator_instance, location) +#define LIST_ALLOCATE(the_allocator, allocator_instance, size, hint) std::allocator_traits::allocate(allocator_instance, size, hint) +#define LIST_ALLOCATE_INITIALIZATION(the_allocator, size, hint) std::allocator_traits::allocate(*this, size, hint) +#define LIST_DEALLOCATE(the_allocator, allocator_instance, location, size) std::allocator_traits::deallocate(allocator_instance, location, size) + +#include // std::sort +#include // assert +#include // memmove, memcpy +#include +#include // std::bidirectional_iterator_tag +#include // std::numeric_limits +#include // std::uninitialized_copy, std::allocator +#include // std::is_trivially_destructible, etc +#include // std::move + +namespace cata +{ + +template > class + list : private element_allocator_type +{ + public: + // Standard container typedefs: + using value_type = element_type; + using allocator_type = element_allocator_type; + using group_size_type = unsigned short; + + using size_type = typename std::allocator_traits::size_type; + using difference_type = typename std::allocator_traits::difference_type; + using reference = element_type&; + using const_reference = const element_type&; + using pointer = typename std::allocator_traits::pointer; + using const_pointer = typename std::allocator_traits::const_pointer; + + // Iterator declarations: + template class list_iterator; + using iterator = list_iterator; + using const_iterator = list_iterator; + friend class list_iterator; // Using 'iterator' typedef name here is illegal under C++03 + friend class list_iterator; + + template class list_reverse_iterator; + using reverse_iterator = list_reverse_iterator; + using const_reverse_iterator = list_reverse_iterator; + friend class list_reverse_iterator; + friend class list_reverse_iterator; + + private: + struct group; // forward declarations for typedefs below + struct node; + + using group_allocator_type = typename std::allocator_traits::template + rebind_alloc; + using node_allocator_type = typename std::allocator_traits::template + rebind_alloc; + using group_pointer_type = typename std::allocator_traits::pointer; + using node_pointer_type = typename std::allocator_traits::pointer; + using node_pointer_allocator_type = typename std::allocator_traits::template + rebind_alloc; + + struct node_base { + node_pointer_type next, previous; + + node_base() + {} + + node_base( const node_pointer_type &n, const node_pointer_type &p ): + next( n ), + previous( p ) + {} + + + node_base( node_pointer_type &&n, node_pointer_type &&p ) noexcept: + next( std::move( n ) ), + previous( std::move( p ) ) + {} + }; + + struct node : public node_base { + element_type element; + + node( const node_pointer_type next, const node_pointer_type previous, const element_type &source ): + node_base( next, previous ), + element( source ) + {} + + node( node_pointer_type &&next, node_pointer_type &&previous, element_type &&source ) noexcept: + node_base( std::move( next ), std::move( previous ) ), + element( std::move( source ) ) + {} + + template + node( node_pointer_type const next, node_pointer_type const previous, arguments &&... parameters ): + node_base( next, previous ), + element( std::forward( parameters ) ... ) + {} + }; + + struct group : public node_allocator_type { + node_pointer_type nodes; + node_pointer_type free_list_head; + node_pointer_type beyond_end; + group_size_type number_of_elements; + + group() noexcept: + nodes( NULL ), + free_list_head( NULL ), + beyond_end( NULL ), + number_of_elements( 0 ) + {} + + group( const group_size_type group_size, node_pointer_type const previous = NULL ): + nodes( LIST_ALLOCATE_INITIALIZATION( node_allocator_type, group_size, previous ) ), + free_list_head( NULL ), + beyond_end( nodes + group_size ), + number_of_elements( 0 ) + {} + + // Actually a move operator, used by c++03 in group_vector's remove, expand_capacity and append + group &operator=( const group &source ) noexcept { + nodes = source.nodes; + free_list_head = source.free_list_head; + beyond_end = source.beyond_end; + number_of_elements = source.number_of_elements; + return *this; + } + + group( group &&source ) noexcept: + node_allocator_type( source ), + nodes( std::move( source.nodes ) ), + free_list_head( std::move( source.free_list_head ) ), + beyond_end( std::move( source.beyond_end ) ), + number_of_elements( source.number_of_elements ) { + source.nodes = NULL; + source.beyond_end = NULL; + } + + group &operator=( group &&source ) noexcept { + nodes = std::move( source.nodes ); + free_list_head = std::move( source.free_list_head ); + beyond_end = std::move( source.beyond_end ); + number_of_elements = std::move( source.number_of_elements ); + source.nodes = NULL; + source.beyond_end = NULL; + return *this; + } + + ~group() noexcept { + LIST_DEALLOCATE( node_allocator_type, ( *this ), nodes, + static_cast( beyond_end - nodes ) ); + } + }; + + class group_vector : private node_pointer_allocator_type + { + public: + // last_endpoint_group is the last -active- group in the block. Other -inactive- (previously used, now empty of elements) groups may be stored after this group for future usage (to reduce deallocation/reallocation of nodes). block_pointer + size - 1 == the last group in the block, regardless of whether or not the group is active. + group_pointer_type last_endpoint_group, block_pointer, last_searched_group; + size_type size; + + struct ebco_pair2 : allocator_type { // empty-base-class optimisation + size_type capacity; // Total element capacity of all initialized groups + explicit ebco_pair2( const size_type number_of_elements ) noexcept: capacity( + number_of_elements ) {}; + } element_allocator_pair; + + struct ebco_pair : group_allocator_type { + size_type capacity; // Total group capacity + explicit ebco_pair( const size_type number_of_groups ) noexcept: capacity( number_of_groups ) {}; + } group_allocator_pair; + + group_vector() noexcept: + node_pointer_allocator_type( node_pointer_allocator_type() ), + last_endpoint_group( NULL ), + block_pointer( NULL ), + last_searched_group( NULL ), + size( 0 ), + element_allocator_pair( 0 ), + group_allocator_pair( 0 ) + {} + + inline LIST_FORCE_INLINE void blank() noexcept { + if LIST_CONSTEXPR( std::is_trivial::value ) { + std::memset( static_cast( this ), 0, sizeof( group_vector ) ); + } else { + last_endpoint_group = NULL; + block_pointer = NULL; + last_searched_group = NULL; + size = 0; + element_allocator_pair.capacity = 0; + group_allocator_pair.capacity = 0; + } + } + + group_vector( group_vector &&source ) noexcept: + last_endpoint_group( std::move( source.last_endpoint_group ) ), + block_pointer( std::move( source.block_pointer ) ), + last_searched_group( std::move( source.last_searched_group ) ), + size( source.size ), + element_allocator_pair( source.element_allocator_pair.capacity ), + group_allocator_pair( source.group_allocator_pair.capacity ) { + source.blank(); + } + + group_vector &operator = ( group_vector &&source ) noexcept { + if LIST_CONSTEXPR( std::is_trivial::value ) { + std::memcpy( static_cast( this ), &source, sizeof( group_vector ) ); + } else { + last_endpoint_group = std::move( source.last_endpoint_group ); + block_pointer = std::move( source.block_pointer ); + last_searched_group = std::move( source.last_searched_group ); + size = source.size; + element_allocator_pair.capacity = source.element_allocator_pair.capacity; + group_allocator_pair.capacity = source.group_allocator_pair.capacity; + } + + source.blank(); + return *this; + } + + ~group_vector() noexcept + {} + + void destroy_all_data( const node_pointer_type last_endpoint_node ) noexcept { + if( block_pointer == NULL ) { + return; + } + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value || + !std::is_trivially_destructible::value ) { + clear( last_endpoint_node ); // If clear has already been called, last_endpoint_node will already be == block_pointer->nodes, so no work will occur + } + + const group_pointer_type end_group = block_pointer + size; + for( group_pointer_type current_group = block_pointer; current_group != end_group; + ++current_group ) { + LIST_DESTROY( group_allocator_type, group_allocator_pair, current_group ); + } + + LIST_DEALLOCATE( group_allocator_type, group_allocator_pair, block_pointer, + group_allocator_pair.capacity ); + blank(); + } + + void clear( const node_pointer_type last_endpoint_node ) noexcept { + for( group_pointer_type current_group = block_pointer; current_group != last_endpoint_group; + ++current_group ) { + if LIST_CONSTEXPR( !std::is_trivially_destructible::value || + !std::is_trivially_destructible::value ) { + const node_pointer_type end = current_group->beyond_end; + + if( ( end - current_group->nodes ) != current_group->number_of_elements ) { + // If there are erased nodes present in the group + for( node_pointer_type current_node = current_group->nodes; current_node != end; ++current_node ) { + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + if( current_node->next != NULL ) { // ie. is not part of free list + LIST_DESTROY( element_allocator_type, element_allocator_pair, &( current_node->element ) ); + } + } + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->next ) ); + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->previous ) ); + } + } + } else { + for( node_pointer_type current_node = current_group->nodes; current_node != end; ++current_node ) { + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( element_allocator_type, element_allocator_pair, &( current_node->element ) ); + } + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->next ) ); + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->previous ) ); + } + } + } + } + + current_group->free_list_head = NULL; + current_group->number_of_elements = 0; + } + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value || + !std::is_trivially_destructible::value ) { + if( ( last_endpoint_node - last_endpoint_group->nodes ) != + last_endpoint_group->number_of_elements ) { + // If there are erased nodes present in the group + for( node_pointer_type current_node = last_endpoint_group->nodes; + current_node != last_endpoint_node; ++current_node ) { + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + if( current_node->next != NULL ) { + // is not part of free list ie. element has not already had it's destructor called + LIST_DESTROY( element_allocator_type, element_allocator_pair, &( current_node->element ) ); + } + } + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->next ) ); + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->previous ) ); + } + } + } else { + for( node_pointer_type current_node = last_endpoint_group->nodes; + current_node != last_endpoint_node; ++current_node ) { + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( element_allocator_type, element_allocator_pair, &( current_node->element ) ); + } + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->next ) ); + LIST_DESTROY( node_pointer_allocator_type, ( *this ), &( current_node->previous ) ); + } + } + } + } + + last_endpoint_group->free_list_head = NULL; + last_endpoint_group->number_of_elements = 0; + last_searched_group = last_endpoint_group = block_pointer; + } + + void expand_capacity( const size_type new_capacity ) { // used by add_new and append + group_pointer_type const old_block = block_pointer; + block_pointer = LIST_ALLOCATE( group_allocator_type, group_allocator_pair, new_capacity, 0 ); + + if LIST_CONSTEXPR( std::is_trivially_copyable::value && + std::is_trivially_destructible::value ) { + // Dereferencing here in order to deal with smart pointer situations ie. obtaining the raw pointer from the smart pointer + // reinterpret_cast necessary to deal with GCC 8 warnings + std::memcpy( static_cast( &*block_pointer ), static_cast( &*old_block ), + sizeof( group ) * size ); + } else if LIST_CONSTEXPR( std::is_move_constructible::value ) { + std::uninitialized_copy( std::make_move_iterator( old_block ), + std::make_move_iterator( old_block + size ), block_pointer ); + } else { + // If allocator supplies non-trivial pointers it becomes necessary to destroy the group. uninitialized_copy will not work in this context as the copy constructor for "group" is overriden in C++03/98. The = operator for "group" has been overriden to make the following work: + const group_pointer_type beyond_end = old_block + size; + group_pointer_type current_new_group = block_pointer; + + for( group_pointer_type current_group = old_block; current_group != beyond_end; ++current_group ) { + *( current_new_group++ ) = *( current_group ); + + current_group->nodes = NULL; + current_group->beyond_end = NULL; + LIST_DESTROY( group_allocator_type, group_allocator_pair, current_group ); + } + } + + // correct pointer post-reallocation + last_searched_group = block_pointer + ( last_searched_group - old_block ); + LIST_DEALLOCATE( group_allocator_type, group_allocator_pair, old_block, + group_allocator_pair.capacity ); + group_allocator_pair.capacity = new_capacity; + } + + void add_new( const group_size_type group_size ) { + if( group_allocator_pair.capacity == size ) { + expand_capacity( group_allocator_pair.capacity * 2 ); + } + + last_endpoint_group = block_pointer + size - 1; + + LIST_CONSTRUCT( group_allocator_type, group_allocator_pair, last_endpoint_group + 1, group_size, + last_endpoint_group->nodes ); + + ++last_endpoint_group; // Doing this here instead of pre-construct to avoid need for a try-catch block + element_allocator_pair.capacity += group_size; + ++size; + } + + // For adding first group *only* when group vector is completely empty and block_pointer is NULL + void initialize( const group_size_type group_size ) { + last_endpoint_group = block_pointer = last_searched_group = LIST_ALLOCATE( group_allocator_type, + group_allocator_pair, 1, 0 ); + group_allocator_pair.capacity = 1; + + LIST_CONSTRUCT( group_allocator_type, group_allocator_pair, last_endpoint_group, group_size ); + + size = 1; // Doing these here instead of pre-construct to avoid need for a try-catch block + element_allocator_pair.capacity = group_size; + } + + void remove( group_pointer_type const group_to_erase ) noexcept { + if( last_searched_group >= group_to_erase && last_searched_group != block_pointer ) { + --last_searched_group; + } + + element_allocator_pair.capacity -= static_cast( group_to_erase->beyond_end - + group_to_erase->nodes ); + + LIST_DESTROY( group_allocator_type, group_allocator_pair, group_to_erase ); + + if LIST_CONSTEXPR( std::is_trivially_copyable::value && + std::is_trivially_destructible::value ) { + // Dereferencing here in order to deal with smart pointer situations ie. obtaining the raw pointer from the smart pointer + std::memmove( static_cast( &*group_to_erase ), static_cast( &*group_to_erase + 1 ), + sizeof( group ) * ( --size - static_cast( &*group_to_erase - &*block_pointer ) ) ); + } else if LIST_CONSTEXPR( std::is_move_constructible::value ) { + std::move( group_to_erase + 1, block_pointer + size--, group_to_erase ); + } else { + group_pointer_type back = block_pointer + size--; + std::copy( group_to_erase + 1, back--, group_to_erase ); + + back->nodes = NULL; + back->beyond_end = NULL; + LIST_DESTROY( group_allocator_type, group_allocator_pair, back ); + } + } + + void move_to_back( group_pointer_type const group_to_erase ) { + if( last_searched_group >= group_to_erase && last_searched_group != block_pointer ) { + --last_searched_group; + } + + group *temp_group = LIST_ALLOCATE( group_allocator_type, group_allocator_pair, 1, NULL ); + + if LIST_CONSTEXPR( std::is_trivially_copyable::value && + std::is_trivially_destructible::value ) { + std::memcpy( static_cast( &*temp_group ), static_cast( &*group_to_erase ), + sizeof( group ) ); + std::memmove( static_cast( &*group_to_erase ), static_cast( &*group_to_erase + 1 ), + sizeof( group ) * ( ( size - 1 ) - static_cast( &*group_to_erase - &*block_pointer ) ) ); + std::memcpy( static_cast( &*( block_pointer + size - 1 ) ), + static_cast( &*temp_group ), sizeof( group ) ); + } else if LIST_CONSTEXPR( std::is_move_constructible::value ) { + LIST_CONSTRUCT( group_allocator_type, group_allocator_pair, temp_group, + std::move( *group_to_erase ) ); + std::move( group_to_erase + 1, block_pointer + size, group_to_erase ); + *( block_pointer + size - 1 ) = std::move( *temp_group ); + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( group_allocator_type, group_allocator_pair, temp_group ); + } + } else { + LIST_CONSTRUCT( group_allocator_type, group_allocator_pair, temp_group, group() ); + + *temp_group = *group_to_erase; + std::copy( group_to_erase + 1, block_pointer + size, group_to_erase ); + *( block_pointer + --size ) = *temp_group; + + temp_group->nodes = NULL; + LIST_DESTROY( group_allocator_type, group_allocator_pair, temp_group ); + } + + LIST_DEALLOCATE( group_allocator_type, group_allocator_pair, temp_group, 1 ); + } + + // In working implementation this cannot throw + group_pointer_type get_nearest_freelist_group( const node_pointer_type location_node ) noexcept { + const group_pointer_type beyond_end_group = last_endpoint_group + 1; + group_pointer_type left = last_searched_group - 1, right = last_searched_group + 1, + freelist_group = NULL; + bool right_not_beyond_back = ( right < beyond_end_group ); + bool left_not_beyond_front = ( left >= block_pointer ); + + + // ie. location is within last_search_group + if( location_node >= last_searched_group->nodes && + location_node < last_searched_group->beyond_end ) { + // if last_searched_group has previously-erased nodes + if( last_searched_group->free_list_head != NULL ) { + return last_searched_group; + } + } else { // search for the node group which location_node is located within, using last_searched_group as a starting point and searching left and right. Try and find the closest node group with reusable erased-element locations along the way: + group_pointer_type closest_freelist_left = ( last_searched_group->free_list_head == NULL ) ? NULL : + last_searched_group, closest_freelist_right = ( last_searched_group->free_list_head == NULL ) ? + NULL : last_searched_group; + + while( true ) { + if( right_not_beyond_back ) { + // location_node's group is found + if( ( location_node < right->beyond_end ) && ( location_node >= right->nodes ) ) { + // group has erased nodes, reuse them: + if( right->free_list_head != NULL ) { + last_searched_group = right; + return right; + } + + difference_type left_distance; + + if( closest_freelist_right != NULL ) { + last_searched_group = right; + left_distance = right - closest_freelist_right; + + if( left_distance <= 2 ) { // ie. this group is close enough to location_node's group + return closest_freelist_right; + } + + freelist_group = closest_freelist_right; + } else { + last_searched_group = right; + left_distance = right - left; + } + + + // Otherwise find closest group with freelist - check an equal distance on the right to the distance we've checked on the left: + const group_pointer_type end_group = ( ( ( right + left_distance ) > beyond_end_group ) ? + beyond_end_group : ( right + left_distance - 1 ) ); + + while( ++right != end_group ) { + if( right->free_list_head != NULL ) { + return right; + } + } + + if( freelist_group != NULL ) { + return freelist_group; + } + + right_not_beyond_back = ( right < beyond_end_group ); + break; // group with reusable erased nodes not found yet, continue searching in loop below + } + + // location_node's group not found, but a reusable location found + if( right->free_list_head != NULL ) { + if( ( closest_freelist_right == NULL ) & ( closest_freelist_left == NULL ) ) { + closest_freelist_left = right; + } + + closest_freelist_right = right; + } + + right_not_beyond_back = ( ++right < beyond_end_group ); + } + + if( left_not_beyond_front ) { + if( ( location_node >= left->nodes ) && ( location_node < left->beyond_end ) ) { + if( left->free_list_head != NULL ) { + last_searched_group = left; + return left; + } + + difference_type right_distance; + + if( closest_freelist_left != NULL ) { + last_searched_group = left; + right_distance = closest_freelist_left - left; + + if( right_distance <= 2 ) { + return closest_freelist_left; + } + + freelist_group = closest_freelist_left; + } else { + last_searched_group = left; + right_distance = right - left; + } + + // Otherwise find closest group with freelist: + const group_pointer_type end_group = ( ( ( left - right_distance ) < block_pointer ) ? block_pointer + - 1 : ( left - right_distance ) + 1 ); + + while( --left != end_group ) { + if( left->free_list_head != NULL ) { + return left; + } + } + + if( freelist_group != NULL ) { + return freelist_group; + } + + left_not_beyond_front = ( left >= block_pointer ); + break; + } + + if( left->free_list_head != NULL ) { + if( ( closest_freelist_left == NULL ) & ( closest_freelist_right == NULL ) ) { + closest_freelist_right = left; + } + + closest_freelist_left = left; + } + + left_not_beyond_front = ( --left >= block_pointer ); + } + } + } + + // The node group which location_node is located within, is known at this point. Continue searching outwards from this group until a group is found with a reusable location: + while( true ) { + if( right_not_beyond_back ) { + if( right->free_list_head != NULL ) { + return right; + } + + right_not_beyond_back = ( ++right < beyond_end_group ); + } + + if( left_not_beyond_front ) { + if( left->free_list_head != NULL ) { + return left; + } + + left_not_beyond_front = ( --left >= block_pointer ); + } + } + + // Will never reach here on functioning implementations + } + + void swap( group_vector &source ) LIST_NOEXCEPT_SWAP( group_allocator_type ) { + if LIST_CONSTEXPR( + std::is_trivial::value ) { // if all pointer types are trivial we can just copy using memcpy - faster - avoids constructors/destructors etc + char temp[sizeof( group_vector )]; + std::memcpy( static_cast( &temp ), static_cast( this ), sizeof( group_vector ) ); + std::memcpy( static_cast( this ), static_cast( &source ), sizeof( group_vector ) ); + std::memcpy( static_cast( &source ), static_cast( &temp ), sizeof( group_vector ) ); + } else { + const group_pointer_type swap_last_endpoint_group = last_endpoint_group, + swap_block_pointer = block_pointer, swap_last_searched_group = last_searched_group; + const size_type swap_size = size, swap_element_capacity = element_allocator_pair.capacity, + swap_capacity = group_allocator_pair.capacity; + + last_endpoint_group = source.last_endpoint_group; + block_pointer = source.block_pointer; + last_searched_group = source.last_searched_group; + size = source.size; + element_allocator_pair.capacity = source.element_allocator_pair.capacity; + group_allocator_pair.capacity = source.group_allocator_pair.capacity; + + source.last_endpoint_group = swap_last_endpoint_group; + source.block_pointer = swap_block_pointer; + source.last_searched_group = swap_last_searched_group; + source.size = swap_size; + source.element_allocator_pair.capacity = swap_element_capacity; + source.group_allocator_pair.capacity = swap_capacity; + } + } + + void trim_trailing_groups() noexcept { + const group_pointer_type beyond_last = block_pointer + size; + + for( group_pointer_type current_group = last_endpoint_group + 1; current_group != beyond_last; + ++current_group ) { + element_allocator_pair.capacity -= static_cast( current_group->beyond_end - + current_group->nodes ); + LIST_DESTROY( group_allocator_type, group_allocator_pair, current_group ); + } + + size -= static_cast( beyond_last - ( last_endpoint_group + 1 ) ); + } + + void append( group_vector &source ) { + source.trim_trailing_groups(); + trim_trailing_groups(); + + if( size + source.size > group_allocator_pair.capacity ) { + expand_capacity( size + source.size ); + } + + if LIST_CONSTEXPR( std::is_trivially_copyable::value && + std::is_trivially_destructible::value ) { + // &* in order to deal with smart pointer situations ie. obtaining the raw pointer from the smart pointer + std::memcpy( static_cast( &*block_pointer + size ), + static_cast( &*source.block_pointer ), sizeof( group ) * source.size ); + } else if LIST_CONSTEXPR( std::is_move_constructible::value ) { + std::uninitialized_copy( std::make_move_iterator( source.block_pointer ), + std::make_move_iterator( source.block_pointer + source.size ), block_pointer + size ); + } else { + group_pointer_type current_new_group = block_pointer + size; + const group_pointer_type beyond_end_source = source.block_pointer + source.size; + + for( group_pointer_type current_group = source.block_pointer; current_group != beyond_end_source; + ++current_group ) { + *( current_new_group++ ) = *( current_group ); + + current_group->nodes = NULL; + current_group->beyond_end = NULL; + LIST_DESTROY( group_allocator_type, source.group_allocator_pair, current_group ); + } + } + + LIST_DEALLOCATE( group_allocator_type, source.group_allocator_pair, source.block_pointer, + source.group_allocator_pair.capacity ); + size += source.size; + last_endpoint_group = block_pointer + size - 1; + element_allocator_pair.capacity += source.element_allocator_pair.capacity; + source.blank(); + } + }; + + // Implement const/non-const iterator switching pattern: + template struct choose; + + template struct choose { + using type = IsTrue; + }; + + template struct choose { + using type = IsFalse; + }; + + public: + + template class list_iterator + { + private: + node_pointer_type node_pointer; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename list::value_type; + using difference_type = typename list::difference_type; + using pointer = typename + choose::type; + using reference = typename + choose::type; + + friend class list; + + inline LIST_FORCE_INLINE bool operator==( const list_iterator rh ) const noexcept { + return ( node_pointer == rh.node_pointer ); + } + + inline LIST_FORCE_INLINE bool operator==( const list_iterator < !is_const > rh ) const + noexcept { + return ( node_pointer == rh.node_pointer ); + } + + inline LIST_FORCE_INLINE bool operator!=( const list_iterator rh ) const noexcept { + return ( node_pointer != rh.node_pointer ); + } + + inline LIST_FORCE_INLINE bool operator!=( const list_iterator < !is_const > rh ) const + noexcept { + return ( node_pointer != rh.node_pointer ); + } + + inline LIST_FORCE_INLINE reference operator*() const { + return node_pointer->element; + } + + inline LIST_FORCE_INLINE pointer operator->() const { + return &( node_pointer->element ); + } + + inline LIST_FORCE_INLINE list_iterator &operator++() noexcept { + assert( node_pointer != NULL ); // covers uninitialised list_iterator + node_pointer = node_pointer->next; + return *this; + } + + inline list_iterator operator++( int ) noexcept { + const list_iterator copy( *this ); + ++*this; + return copy; + } + + inline LIST_FORCE_INLINE list_iterator &operator--() noexcept { + assert( node_pointer != NULL ); // covers uninitialised list_iterator + node_pointer = node_pointer->previous; + return *this; + } + + inline list_iterator operator--( int ) noexcept { + const list_iterator copy( *this ); + --*this; + return copy; + } + + inline list_iterator &operator=( const list_iterator &rh ) noexcept { + node_pointer = rh.node_pointer; + return *this; + } + + inline list_iterator &operator=( const list_iterator < !is_const > &rh ) noexcept { + node_pointer = rh.node_pointer; + return *this; + } + + inline list_iterator &operator=( const list_iterator &&rh ) noexcept { + node_pointer = std::move( rh.node_pointer ); + return *this; + } + + inline list_iterator &operator=( const list_iterator < !is_const > &&rh ) noexcept { + node_pointer = std::move( rh.node_pointer ); + return *this; + } + + list_iterator() noexcept: node_pointer( NULL ) {} + + list_iterator( const list_iterator &source ) noexcept: node_pointer( source.node_pointer ) {} + + list_iterator( const list_iterator < !is_const > &source ) noexcept: node_pointer( + source.node_pointer ) {} + + list_iterator( const list_iterator &&source ) noexcept: node_pointer( std::move( + source.node_pointer ) ) {} + + list_iterator( const list_iterator < !is_const > && + source ) noexcept: node_pointer( std::move( source.node_pointer ) ) {} + + private: + + list_iterator( const node_pointer_type node_p ) noexcept: node_pointer( node_p ) {} + }; + + template class list_reverse_iterator + { + private: + node_pointer_type node_pointer; + + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = typename list::value_type; + using difference_type = typename list::difference_type; + using pointer = typename + choose::type; + using reference = typename + choose::type; + + friend class list; + + inline LIST_FORCE_INLINE bool operator==( const list_reverse_iterator rh ) const noexcept { + return ( node_pointer == rh.node_pointer ); + } + + inline LIST_FORCE_INLINE bool operator==( const list_reverse_iterator < !is_const > rh ) const + noexcept { + return ( node_pointer == rh.node_pointer ); + } + + inline LIST_FORCE_INLINE bool operator!=( const list_reverse_iterator rh ) const noexcept { + return ( node_pointer != rh.node_pointer ); + } + + inline LIST_FORCE_INLINE bool operator!=( const list_reverse_iterator < !is_const > rh ) const + noexcept { + return ( node_pointer != rh.node_pointer ); + } + + inline LIST_FORCE_INLINE reference operator*() const { + return node_pointer->element; + } + + inline LIST_FORCE_INLINE pointer operator->() const { + return &( node_pointer->element ); + } + + inline LIST_FORCE_INLINE list_reverse_iterator &operator++() noexcept { + assert( node_pointer != NULL ); // covers uninitialised list_reverse_iterator + node_pointer = node_pointer->previous; + return *this; + } + + inline list_reverse_iterator operator++( int ) noexcept { + const list_reverse_iterator copy( *this ); + ++*this; + return copy; + } + + inline LIST_FORCE_INLINE list_reverse_iterator &operator--() noexcept { + assert( node_pointer != NULL ); + node_pointer = node_pointer->next; + return *this; + } + + inline list_reverse_iterator operator--( int ) noexcept { + const list_reverse_iterator copy( *this ); + --*this; + return copy; + } + + inline list_reverse_iterator &operator=( const list_reverse_iterator &rh ) noexcept { + node_pointer = rh.node_pointer; + return *this; + } + + inline list_reverse_iterator &operator=( const list_reverse_iterator < !is_const > &rh ) + noexcept { + node_pointer = rh.node_pointer; + return *this; + } + + inline list_reverse_iterator &operator=( const list_reverse_iterator &&rh ) noexcept { + node_pointer = std::move( rh.node_pointer ); + return *this; + } + + inline list_reverse_iterator &operator=( const list_reverse_iterator < !is_const > && + rh ) noexcept { + node_pointer = std::move( rh.node_pointer ); + return *this; + } + + inline typename list::iterator base() const noexcept { + return typename list::iterator( node_pointer->next ); + } + + list_reverse_iterator() noexcept: node_pointer( NULL ) {} + + list_reverse_iterator( const list_reverse_iterator &source ) noexcept: node_pointer( + source.node_pointer ) {} + + list_reverse_iterator( const list_reverse_iterator &&source ) noexcept: node_pointer( std::move( + source.node_pointer ) ) {} + + private: + + list_reverse_iterator( const node_pointer_type node_p ) noexcept: node_pointer( node_p ) {} + }; + + private: + + // Used by range-insert and range-constructor to prevent fill-insert and fill-constructor function calls mistakenly resolving to the range insert/constructor + template + struct plf_enable_if_c { + using type = T; + }; + + template + struct plf_enable_if_c { + }; + + group_vector groups; + node_base end_node; + node_pointer_type + last_endpoint; // last_endpoint being NULL means no elements have been constructed, but there may still be groups available due to clear() or reservee() + iterator end_iterator; // end_iterator is always the last entry point in last group in list (or one past the end of group) + iterator begin_iterator; + + // Packaging the group allocator with least-used member variables, for empty-base-class optimisation + struct ebco_pair1 : node_pointer_allocator_type { + size_type total_number_of_elements; + explicit ebco_pair1( const size_type total_num_elements ) noexcept: total_number_of_elements( + total_num_elements ) {} + } node_pointer_allocator_pair; + + struct ebco_pair2 : node_allocator_type { + size_type number_of_erased_nodes; + explicit ebco_pair2( const size_type num_erased_nodes ) noexcept: number_of_erased_nodes( + num_erased_nodes ) {} + } node_allocator_pair; + + public: + + // Default constructor: + list() noexcept: + element_allocator_type( element_allocator_type() ), + end_node( reinterpret_cast( &end_node ), + reinterpret_cast( &end_node ) ), + last_endpoint( NULL ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( reinterpret_cast( &end_node ) ), + node_pointer_allocator_pair( 0 ), + node_allocator_pair( 0 ) + {} + + // Allocator-extended constructor: + explicit list( const element_allocator_type &alloc ): + element_allocator_type( alloc ), + end_node( reinterpret_cast( &end_node ), + reinterpret_cast( &end_node ) ), + last_endpoint( NULL ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( reinterpret_cast( &end_node ) ), + node_pointer_allocator_pair( 0 ), + node_allocator_pair( 0 ) + {} + + // Copy constructor: + list( const list &source ): + element_allocator_type( source ), + end_node( reinterpret_cast( &end_node ), + reinterpret_cast( &end_node ) ), + last_endpoint( NULL ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( reinterpret_cast( &end_node ) ), + node_pointer_allocator_pair( 0 ), + node_allocator_pair( 0 ) { + reserve( source.node_pointer_allocator_pair.total_number_of_elements ); + insert( end_iterator, source.begin_iterator, source.end_iterator ); + } + + // Allocator-extended copy constructor: + list( const list &source, const allocator_type &alloc ): + element_allocator_type( alloc ), + end_node( reinterpret_cast( &end_node ), + reinterpret_cast( &end_node ) ), + last_endpoint( NULL ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( reinterpret_cast( &end_node ) ), + node_pointer_allocator_pair( 0 ), + node_allocator_pair( 0 ) { + reserve( source.node_pointer_allocator_pair.total_number_of_elements ); + insert( end_iterator, source.begin_iterator, source.end_iterator ); + } + + // Move constructor: + list( list &&source ) noexcept: + element_allocator_type( source ), + groups( std::move( source.groups ) ), + end_node( std::move( source.end_node ) ), + last_endpoint( std::move( source.last_endpoint ) ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( ( source.begin_iterator.node_pointer == source.end_iterator.node_pointer ) ? + reinterpret_cast( &end_node ) : std::move( source.begin_iterator ) ), + node_pointer_allocator_pair( source.node_pointer_allocator_pair.total_number_of_elements ), + node_allocator_pair( source.node_allocator_pair.number_of_erased_nodes ) { + end_node.previous->next = begin_iterator.node_pointer->previous = end_iterator.node_pointer; + source.groups.blank(); + source.reset(); + } + + // Allocator-extended move constructor: + list( list &&source, const allocator_type &alloc ): + element_allocator_type( alloc ), + groups( std::move( source.groups ) ), + end_node( std::move( source.end_node ) ), + last_endpoint( std::move( source.last_endpoint ) ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( ( source.begin_iterator.node_pointer == source.end_iterator.node_pointer ) ? + reinterpret_cast( &end_node ) : std::move( source.begin_iterator ) ), + node_pointer_allocator_pair( source.node_pointer_allocator_pair.total_number_of_elements ), + node_allocator_pair( source.node_allocator_pair.number_of_erased_nodes ) { + end_node.previous->next = begin_iterator.node_pointer->previous = end_iterator.node_pointer; + source.groups.blank(); + source.reset(); + } + + // Fill constructor: + list( const size_type fill_number, const element_type &element, + const element_allocator_type &alloc = element_allocator_type() ): + element_allocator_type( alloc ), + end_node( reinterpret_cast( &end_node ), + reinterpret_cast( &end_node ) ), + last_endpoint( NULL ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( reinterpret_cast( &end_node ) ), + node_pointer_allocator_pair( 0 ), + node_allocator_pair( 0 ) { + reserve( fill_number ); + insert( end_iterator, fill_number, element ); + } + + // Range constructor: + template + list( const typename plf_enable_if_c < !std::numeric_limits::is_integer, + iterator_type >::type &first, const iterator_type &last, + const element_allocator_type &alloc = element_allocator_type() ): + element_allocator_type( alloc ), + end_node( reinterpret_cast( &end_node ), + reinterpret_cast( &end_node ) ), + last_endpoint( NULL ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( reinterpret_cast( &end_node ) ), + node_pointer_allocator_pair( 0 ), + node_allocator_pair( 0 ) { + insert( end_iterator, first, last ); + } + + // Initializer-list constructor: + list( const std::initializer_list &element_list, + const element_allocator_type &alloc = element_allocator_type() ): + element_allocator_type( alloc ), + end_node( reinterpret_cast( &end_node ), + reinterpret_cast( &end_node ) ), + last_endpoint( NULL ), + end_iterator( reinterpret_cast( &end_node ) ), + begin_iterator( reinterpret_cast( &end_node ) ), + node_pointer_allocator_pair( 0 ), + node_allocator_pair( 0 ) { + reserve( element_list.size() ); + insert( end_iterator, element_list ); + } + + ~list() noexcept { + groups.destroy_all_data( last_endpoint ); + } + + inline iterator begin() noexcept { + return begin_iterator; + } + + inline const_iterator begin() const noexcept { + return begin_iterator; + } + + inline iterator end() noexcept { + return end_iterator; + } + + inline const_iterator end() const noexcept { + return end_iterator; + } + + inline const_iterator cbegin() const noexcept { + return const_iterator( begin_iterator.node_pointer ); + } + + inline const_iterator cend() const noexcept { + return const_iterator( end_iterator.node_pointer ); + } + + inline reverse_iterator rbegin() const noexcept { + return reverse_iterator( end_node.previous ); + } + + inline reverse_iterator rend() const noexcept { + return reverse_iterator( end_iterator.node_pointer ); + } + + inline const_reverse_iterator crbegin() const noexcept { + return const_reverse_iterator( end_node.previous ); + } + + inline const_reverse_iterator crend() const noexcept { + return const_reverse_iterator( end_iterator.node_pointer ); + } + + inline reference front() { + assert( begin_iterator.node_pointer != &end_node ); + return begin_iterator.node_pointer->element; + } + + inline const_reference front() const { + assert( begin_iterator.node_pointer != &end_node ); + return begin_iterator.node_pointer->element; + } + + inline reference back() { + assert( end_node.previous != &end_node ); + return end_node.previous->element; + } + + inline const_reference back() const { + assert( end_node.previous != &end_node ); + return end_node.previous->element; + } + + void clear() noexcept { + if( last_endpoint == NULL ) { + return; + } + + if( node_pointer_allocator_pair.total_number_of_elements != 0 ) { + groups.clear( last_endpoint ); + } + + end_node.next = reinterpret_cast( &end_node ); + end_node.previous = reinterpret_cast( &end_node ); + last_endpoint = groups.block_pointer->nodes; + begin_iterator.node_pointer = end_iterator.node_pointer; + node_pointer_allocator_pair.total_number_of_elements = 0; + node_allocator_pair.number_of_erased_nodes = 0; + } + + private: + + void reset() noexcept { + groups.destroy_all_data( last_endpoint ); + last_endpoint = NULL; + end_node.next = reinterpret_cast( &end_node ); + end_node.previous = reinterpret_cast( &end_node ); + begin_iterator.node_pointer = end_iterator.node_pointer; + node_pointer_allocator_pair.total_number_of_elements = 0; + node_allocator_pair.number_of_erased_nodes = 0; + } + + public: + + iterator insert( const iterator it, const element_type &element ) { + // ie. list is not empty + if( last_endpoint != NULL ) { + // No erased nodes available for reuse + if( node_allocator_pair.number_of_erased_nodes == 0 ) { + // last_endpoint is beyond the end of a group + if( last_endpoint == groups.last_endpoint_group->beyond_end ) { + // ie. there are no reusable groups available at the back of group vector + if( static_cast( groups.last_endpoint_group - groups.block_pointer ) == groups.size - + 1 ) { + groups.add_new( ( node_pointer_allocator_pair.total_number_of_elements < LIST_BLOCK_MAX ) ? + static_cast( node_pointer_allocator_pair.total_number_of_elements ) : + LIST_BLOCK_MAX ); + } else { + ++groups.last_endpoint_group; + } + + last_endpoint = groups.last_endpoint_group->nodes; + } + + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint, it.node_pointer, + it.node_pointer->previous, element ); + + ++( groups.last_endpoint_group->number_of_elements ); + ++node_pointer_allocator_pair.total_number_of_elements; + + if( it.node_pointer == begin_iterator.node_pointer ) { + begin_iterator.node_pointer = last_endpoint; + } + + it.node_pointer->previous->next = last_endpoint; + it.node_pointer->previous = last_endpoint; + + return iterator( last_endpoint++ ); + } else { + group_pointer_type const node_group = groups.get_nearest_freelist_group( ( + it.node_pointer != end_iterator.node_pointer ) ? it.node_pointer : end_node.previous ); + node_pointer_type const selected_node = node_group->free_list_head; + const node_pointer_type previous = node_group->free_list_head->previous; + + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, selected_node, it.node_pointer, + it.node_pointer->previous, element ); + + node_group->free_list_head = previous; + ++( node_group->number_of_elements ); + ++node_pointer_allocator_pair.total_number_of_elements; + --node_allocator_pair.number_of_erased_nodes; + + it.node_pointer->previous->next = selected_node; + it.node_pointer->previous = selected_node; + + if( it.node_pointer == begin_iterator.node_pointer ) { + begin_iterator.node_pointer = selected_node; + } + + return iterator( selected_node ); + } + } else { // list is empty + // In case of prior reserve/clear call as opposed to being uninitialized + if( groups.block_pointer == NULL ) { + groups.initialize( LIST_BLOCK_MIN ); + } + + groups.last_endpoint_group->number_of_elements = 1; + end_node.next = end_node.previous = last_endpoint = begin_iterator.node_pointer = + groups.last_endpoint_group->nodes; + node_pointer_allocator_pair.total_number_of_elements = 1; + + if LIST_CONSTEXPR( + std::is_nothrow_copy_constructible::value ) { // Avoid try-catch code generation + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint++, + end_iterator.node_pointer, end_iterator.node_pointer, element ); + } else { + try { + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint++, + end_iterator.node_pointer, end_iterator.node_pointer, element ); + } catch( ... ) { + reset(); + throw; + } + } + + return begin_iterator; + } + } + + inline LIST_FORCE_INLINE void push_back( const element_type &element ) { + insert( end_iterator, element ); + } + + inline LIST_FORCE_INLINE void push_front( const element_type &element ) { + insert( begin_iterator, element ); + } + + // This is almost identical to the insert implementation above with the only change being std::move of the element + iterator insert( const iterator it, element_type &&element ) { + if( last_endpoint != NULL ) { + if( node_allocator_pair.number_of_erased_nodes == 0 ) { + if( last_endpoint == groups.last_endpoint_group->beyond_end ) { + if( static_cast( groups.last_endpoint_group - groups.block_pointer ) == groups.size - + 1 ) { + groups.add_new( ( node_pointer_allocator_pair.total_number_of_elements < LIST_BLOCK_MAX ) ? + static_cast( node_pointer_allocator_pair.total_number_of_elements ) : + LIST_BLOCK_MAX ); + } else { + ++groups.last_endpoint_group; + } + + last_endpoint = groups.last_endpoint_group->nodes; + } + + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint, it.node_pointer, + it.node_pointer->previous, std::move( element ) ); + + ++( groups.last_endpoint_group->number_of_elements ); + ++node_pointer_allocator_pair.total_number_of_elements; + + if( it.node_pointer == begin_iterator.node_pointer ) { + begin_iterator.node_pointer = last_endpoint; + } + + it.node_pointer->previous->next = last_endpoint; + it.node_pointer->previous = last_endpoint; + + return iterator( last_endpoint++ ); + } else { + group_pointer_type const node_group = groups.get_nearest_freelist_group( ( + it.node_pointer != end_iterator.node_pointer ) ? it.node_pointer : end_node.previous ); + node_pointer_type const selected_node = node_group->free_list_head; + const node_pointer_type previous = node_group->free_list_head->previous; + + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, selected_node, it.node_pointer, + it.node_pointer->previous, std::move( element ) ); + + node_group->free_list_head = previous; + ++( node_group->number_of_elements ); + ++node_pointer_allocator_pair.total_number_of_elements; + --node_allocator_pair.number_of_erased_nodes; + + it.node_pointer->previous->next = selected_node; + it.node_pointer->previous = selected_node; + + if( it.node_pointer == begin_iterator.node_pointer ) { + begin_iterator.node_pointer = selected_node; + } + + return iterator( selected_node ); + } + } else { + if( groups.block_pointer == NULL ) { + groups.initialize( LIST_BLOCK_MIN ); + } + + groups.last_endpoint_group->number_of_elements = 1; + end_node.next = end_node.previous = last_endpoint = begin_iterator.node_pointer = + groups.last_endpoint_group->nodes; + node_pointer_allocator_pair.total_number_of_elements = 1; + + if LIST_CONSTEXPR( std::is_nothrow_move_constructible::value ) { + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint++, + end_iterator.node_pointer, end_iterator.node_pointer, std::move( element ) ); + + } else { + try { + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint++, + end_iterator.node_pointer, end_iterator.node_pointer, std::move( element ) ); + } catch( ... ) { + reset(); + throw; + } + } + + return begin_iterator; + } + } + + inline LIST_FORCE_INLINE void push_back( element_type &&element ) { + insert( end_iterator, std::move( element ) ); + } + + inline LIST_FORCE_INLINE void push_front( element_type &&element ) { + insert( begin_iterator, std::move( element ) ); + } + + // This is almost identical to the insert implementations above with the only changes being std::forward of element parameters + template + iterator emplace( const iterator it, arguments &&... parameters ) { + if( last_endpoint != NULL ) { + if( node_allocator_pair.number_of_erased_nodes == 0 ) { + if( last_endpoint == groups.last_endpoint_group->beyond_end ) { + if( static_cast( groups.last_endpoint_group - groups.block_pointer ) == groups.size - + 1 ) { + groups.add_new( ( node_pointer_allocator_pair.total_number_of_elements < LIST_BLOCK_MAX ) ? + static_cast( node_pointer_allocator_pair.total_number_of_elements ) : + LIST_BLOCK_MAX ); + } else { + ++groups.last_endpoint_group; + } + + last_endpoint = groups.last_endpoint_group->nodes; + } + + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint, it.node_pointer, + it.node_pointer->previous, std::forward( parameters )... ); + + ++( groups.last_endpoint_group->number_of_elements ); + ++node_pointer_allocator_pair.total_number_of_elements; + + if( it.node_pointer == begin_iterator.node_pointer ) { + begin_iterator.node_pointer = last_endpoint; + } + + it.node_pointer->previous->next = last_endpoint; + it.node_pointer->previous = last_endpoint; + + return iterator( last_endpoint++ ); + } else { + group_pointer_type const node_group = groups.get_nearest_freelist_group( ( + it.node_pointer != end_iterator.node_pointer ) ? it.node_pointer : end_node.previous ); + node_pointer_type const selected_node = node_group->free_list_head; + const node_pointer_type previous = node_group->free_list_head->previous; + + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, selected_node, it.node_pointer, + it.node_pointer->previous, std::forward( parameters )... ); + + node_group->free_list_head = previous; + ++( node_group->number_of_elements ); + ++node_pointer_allocator_pair.total_number_of_elements; + --node_allocator_pair.number_of_erased_nodes; + + it.node_pointer->previous->next = selected_node; + it.node_pointer->previous = selected_node; + + if( it.node_pointer == begin_iterator.node_pointer ) { + begin_iterator.node_pointer = selected_node; + } + + return iterator( selected_node ); + } + } else { + if( groups.block_pointer == NULL ) { + groups.initialize( LIST_BLOCK_MIN ); + } + + groups.last_endpoint_group->number_of_elements = 1; + end_node.next = end_node.previous = last_endpoint = begin_iterator.node_pointer = + groups.last_endpoint_group->nodes; + node_pointer_allocator_pair.total_number_of_elements = 1; + + if LIST_CONSTEXPR( std::is_nothrow_constructible::value ) { + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint++, + end_iterator.node_pointer, end_iterator.node_pointer, std::forward( parameters )... ); + } else { + try { + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint++, + end_iterator.node_pointer, end_iterator.node_pointer, std::forward( parameters )... ); + } catch( ... ) { + reset(); + throw; + } + } + + return begin_iterator; + } + } + + template + inline LIST_FORCE_INLINE reference emplace_back( arguments &&... parameters ) { + return ( emplace( end_iterator, std::forward( parameters )... ) ).node_pointer->element; + } + + template + inline LIST_FORCE_INLINE reference emplace_front( arguments &&... parameters ) { + return ( emplace( begin_iterator, + std::forward( parameters )... ) ).node_pointer->element; + } + + private: + + void group_fill_position( const element_type &element, group_size_type number_of_elements, + node_pointer_type const position ) { + position->previous->next = last_endpoint; + groups.last_endpoint_group->number_of_elements += number_of_elements; + node_pointer_type previous = position->previous; + + do { + if LIST_CONSTEXPR( std::is_nothrow_copy_constructible::value ) { + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint, last_endpoint + 1, + previous, element ); + } else { + try { + LIST_CONSTRUCT( node_allocator_type, node_allocator_pair, last_endpoint, last_endpoint + 1, + previous, element ); + } catch( ... ) { + previous->next = position; + position->previous = --previous; + groups.last_endpoint_group->number_of_elements -= static_cast + ( number_of_elements - ( last_endpoint - position ) ); + throw; + } + } + + previous = last_endpoint++; + } while( --number_of_elements != 0 ); + + previous->next = position; + position->previous = previous; + } + + public: + + // Fill insert + iterator insert( iterator position, const size_type number_of_elements, + const element_type &element ) { + if( number_of_elements == 0 ) { + return end_iterator; + } else if( number_of_elements == 1 ) { + return insert( position, element ); + } + + if( node_pointer_allocator_pair.total_number_of_elements == 0 && last_endpoint != NULL && + ( static_cast( groups.block_pointer->beyond_end - groups.block_pointer->nodes ) < + number_of_elements ) && + ( static_cast( groups.block_pointer->beyond_end - groups.block_pointer->nodes ) < + LIST_BLOCK_MAX ) ) { + reset(); + } + + if( groups.block_pointer == NULL ) { // ie. Uninitialized list + if( number_of_elements > LIST_BLOCK_MAX ) { + size_type multiples = number_of_elements / LIST_BLOCK_MAX; + const group_size_type remainder = static_cast( number_of_elements - + ( multiples++ * LIST_BLOCK_MAX ) ); // ++ to aid while loop below + + // Create and fill first group: + if( remainder != 0 ) { // make sure smallest block is first + if( remainder >= LIST_BLOCK_MIN ) { + groups.initialize( remainder ); + end_node.next = end_node.previous = last_endpoint = begin_iterator.node_pointer = + groups.last_endpoint_group->nodes; + group_fill_position( element, remainder, end_iterator.node_pointer ); + } else { + // Create first group as BLOCK_MIN size then subtract difference between BLOCK_MIN and remainder from next group: + groups.initialize( LIST_BLOCK_MIN ); + end_node.next = end_node.previous = last_endpoint = begin_iterator.node_pointer = + groups.last_endpoint_group->nodes; + group_fill_position( element, LIST_BLOCK_MIN, end_iterator.node_pointer ); + + groups.add_new( LIST_BLOCK_MAX - ( LIST_BLOCK_MIN - remainder ) ); + end_node.previous = last_endpoint = groups.last_endpoint_group->nodes; + group_fill_position( element, LIST_BLOCK_MAX - ( LIST_BLOCK_MIN - remainder ), + end_iterator.node_pointer ); + --multiples; + } + } else { + groups.initialize( LIST_BLOCK_MAX ); + end_node.next = end_node.previous = last_endpoint = begin_iterator.node_pointer = + groups.last_endpoint_group->nodes; + group_fill_position( element, LIST_BLOCK_MAX, end_iterator.node_pointer ); + --multiples; + } + + while( --multiples != 0 ) { + groups.add_new( LIST_BLOCK_MAX ); + end_node.previous = last_endpoint = groups.last_endpoint_group->nodes; + group_fill_position( element, LIST_BLOCK_MAX, end_iterator.node_pointer ); + } + + } else { + groups.initialize( ( number_of_elements < LIST_BLOCK_MIN ) ? LIST_BLOCK_MIN : + static_cast( number_of_elements ) ); // Construct first group + end_node.next = end_node.previous = last_endpoint = begin_iterator.node_pointer = + groups.last_endpoint_group->nodes; + group_fill_position( element, static_cast( number_of_elements ), + end_iterator.node_pointer ); + } + + node_pointer_allocator_pair.total_number_of_elements = number_of_elements; + return begin_iterator; + } else { + // Insert first element, then use up any erased nodes: + size_type remainder = number_of_elements - 1; + const iterator return_iterator = insert( position, element ); + + while( node_allocator_pair.number_of_erased_nodes != 0 ) { + insert( position, element ); + --node_allocator_pair.number_of_erased_nodes; + + if( --remainder == 0 ) { + return return_iterator; + } + } + + node_pointer_allocator_pair.total_number_of_elements += remainder; + + // then use up remainder of last_endpoint_group: + const group_size_type remaining_nodes_in_group = static_cast + ( groups.last_endpoint_group->beyond_end - last_endpoint ); + + if( remaining_nodes_in_group != 0 ) { + if( remaining_nodes_in_group < remainder ) { + group_fill_position( element, remaining_nodes_in_group, position.node_pointer ); + remainder -= remaining_nodes_in_group; + } else { + group_fill_position( element, static_cast( remainder ), position.node_pointer ); + return return_iterator; + } + } + + // use up trailing groups: + while( ( groups.last_endpoint_group != ( groups.block_pointer + groups.size - 1 ) ) & + ( remainder != 0 ) ) { + last_endpoint = ( ++groups.last_endpoint_group )->nodes; + const group_size_type group_size = static_cast + ( groups.last_endpoint_group->beyond_end - groups.last_endpoint_group->nodes ); + + if( group_size < remainder ) { + group_fill_position( element, group_size, position.node_pointer ); + remainder -= group_size; + } else { + group_fill_position( element, static_cast( remainder ), position.node_pointer ); + return return_iterator; + } + } + + size_type multiples = remainder / static_cast( LIST_BLOCK_MAX ); + remainder -= multiples * LIST_BLOCK_MAX; + + while( multiples-- != 0 ) { + groups.add_new( LIST_BLOCK_MAX ); + last_endpoint = groups.last_endpoint_group->nodes; + group_fill_position( element, LIST_BLOCK_MAX, position.node_pointer ); + } + + if( remainder != + 0 ) { // Bit annoying to create a large block to house a lower number of elements, but beats the alternatives + groups.add_new( LIST_BLOCK_MAX ); + last_endpoint = groups.last_endpoint_group->nodes; + group_fill_position( element, static_cast( remainder ), position.node_pointer ); + } + + return return_iterator; + } + } + + // Range insert + template + iterator insert( const iterator it, + typename plf_enable_if_c < !std::numeric_limits::is_integer, + iterator_type >::type first, const iterator_type last ) { + if( first == last ) { + return end_iterator; + } + + const iterator return_iterator = insert( it, *first ); + + while( ++first != last ) { + insert( it, *first ); + } + + return return_iterator; + } + + // Initializer-list insert + inline iterator insert( const iterator it, + const std::initializer_list &element_list ) { + // use range insert: + return insert( it, element_list.begin(), element_list.end() ); + } + + private: + + inline LIST_FORCE_INLINE void destroy_all_node_pointers( group_pointer_type const + group_to_process, const node_pointer_type beyond_end_node ) noexcept { + for( node_pointer_type current_node = group_to_process->nodes; current_node != beyond_end_node; + ++current_node ) { + LIST_DESTROY( node_pointer_allocator_type, node_pointer_allocator_pair, + &( current_node->next ) ); // Destruct element + LIST_DESTROY( node_pointer_allocator_type, node_pointer_allocator_pair, + &( current_node->previous ) ); // Destruct element + } + } + + public: + + // Single erase: + // if uninitialized/invalid iterator supplied, function could generate an exception, hence no noexcept + iterator erase( const const_iterator it ) { + assert( node_pointer_allocator_pair.total_number_of_elements != 0 ); + assert( it.node_pointer != NULL ); + assert( it.node_pointer != end_iterator.node_pointer ); + + if LIST_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + LIST_DESTROY( element_allocator_type, ( *this ), + &( it.node_pointer->element ) ); // Destruct element + } + + --node_pointer_allocator_pair.total_number_of_elements; + ++node_allocator_pair.number_of_erased_nodes; + + group_pointer_type node_group = groups.last_searched_group; + + // find nearest group with reusable (erased element) memory location: + if( ( it.node_pointer < node_group->nodes ) || ( it.node_pointer >= node_group->beyond_end ) ) { + // Search left and right: + const group_pointer_type beyond_end_group = groups.last_endpoint_group + 1; + group_pointer_type left = node_group - 1; + bool right_not_beyond_back = ( ++node_group < beyond_end_group ); + bool left_not_beyond_front = ( left >= groups.block_pointer ); + + while( true ) { + if( right_not_beyond_back ) { + if( ( it.node_pointer < node_group->beyond_end ) && + ( it.node_pointer >= node_group->nodes ) ) { // usable location found + break; + } + + right_not_beyond_back = ( ++node_group < beyond_end_group ); + } + + if( left_not_beyond_front ) { + if( ( it.node_pointer >= left->nodes ) && + ( it.node_pointer < left->beyond_end ) ) { // usable location found + node_group = left; + break; + } + + left_not_beyond_front = ( --left >= groups.block_pointer ); + } + } + + groups.last_searched_group = node_group; + } + + it.node_pointer->next->previous = it.node_pointer->previous; + it.node_pointer->previous->next = it.node_pointer->next; + + if( it.node_pointer == begin_iterator.node_pointer ) { + begin_iterator.node_pointer = it.node_pointer->next; + } + + const iterator return_iterator( it.node_pointer->next ); + + // ie. group is not empty yet, add node to free list + if( --( node_group->number_of_elements ) != 0 ) { + // next == NULL so that destructor can detect the free list item as opposed to non-free-list item + it.node_pointer->next = NULL; + it.node_pointer->previous = node_group->free_list_head; + node_group->free_list_head = it.node_pointer; + return return_iterator; + } else if( node_group != groups.last_endpoint_group-- ) { + // remove group (and decrement active back group) + const group_size_type group_size = static_cast( node_group->beyond_end - + node_group->nodes ); + node_allocator_pair.number_of_erased_nodes -= group_size; + + if LIST_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + destroy_all_node_pointers( node_group, node_group->beyond_end ); + } + + node_group->free_list_head = NULL; + + // Preserve only last (active) group or second/third-to-last group - seems to be best for performance under high-modification benchmarks + if( ( group_size == LIST_BLOCK_MAX ) | ( node_group >= groups.last_endpoint_group - 1 ) ) { + groups.move_to_back( node_group ); + } else { + groups.remove( node_group ); + } + + return return_iterator; + } else { // clear back group, leave trailing + if LIST_CONSTEXPR( !( std::is_trivially_destructible::value ) ) { + destroy_all_node_pointers( node_group, last_endpoint ); + } + + node_group->free_list_head = NULL; + + if( node_pointer_allocator_pair.total_number_of_elements != 0 ) { + node_allocator_pair.number_of_erased_nodes -= static_cast + ( last_endpoint - node_group->nodes ); + last_endpoint = groups.last_endpoint_group->beyond_end; + } else { + // If number of elements is zero, it indicates that this was the first group in the vector. In which case the last_endpoint_group would be invalid at this point due to the decrement in the above else-if statement. So it needs to be reset, as it will not be reset in the function call below. + groups.last_endpoint_group = groups.block_pointer; + clear(); + } + + return return_iterator; + } + } + + // Range-erase: + // if uninitialized/invalid iterator supplied, function could generate an exception + inline void erase( const const_iterator iterator1, const const_iterator iterator2 ) { + for( const_iterator current = iterator1; current != iterator2; ) { + current = erase( current ); + } + } + + inline void pop_back() { // Exception will occur on empty list + erase( iterator( end_node.previous ) ); + } + + inline void pop_front() { // Exception will occur on empty list + erase( begin_iterator ); + } + + inline list &operator=( const list &source ) { + assert( &source != this ); + + clear(); + reserve( source.node_pointer_allocator_pair.total_number_of_elements ); + insert( end_iterator, source.begin_iterator, source.end_iterator ); + + return *this; + } + + // Move assignment + list &operator=( list &&source ) LIST_NOEXCEPT_MOVE_ASSIGNMENT( allocator_type ) { + assert( &source != this ); + + // Move source values across: + groups.destroy_all_data( last_endpoint ); + + groups = std::move( source.groups ); + end_node = std::move( source.end_node ); + last_endpoint = std::move( source.last_endpoint ); + begin_iterator.node_pointer = ( source.begin_iterator.node_pointer == + source.end_iterator.node_pointer ) ? end_iterator.node_pointer : std::move( + source.begin_iterator.node_pointer ); + node_pointer_allocator_pair.total_number_of_elements = + source.node_pointer_allocator_pair.total_number_of_elements; + node_allocator_pair.number_of_erased_nodes = source.node_allocator_pair.number_of_erased_nodes; + + end_node.previous->next = begin_iterator.node_pointer->previous = end_iterator.node_pointer; + + source.groups.blank(); + source.reset(); + return *this; + } + + bool operator==( const list &rh ) const noexcept { + assert( this != &rh ); + + if( node_pointer_allocator_pair.total_number_of_elements != + rh.node_pointer_allocator_pair.total_number_of_elements ) { + return false; + } + + iterator rh_iterator = rh.begin_iterator; + + for( iterator lh_iterator = begin_iterator; lh_iterator != end_iterator; ) { + if( *rh_iterator++ != *lh_iterator++ ) { + return false; + } + } + + return true; + } + + inline bool operator!=( const list &rh ) const noexcept { + return !( *this == rh ); + } + + inline bool empty() const noexcept { + return node_pointer_allocator_pair.total_number_of_elements == 0; + } + + inline size_type size() const noexcept { + return node_pointer_allocator_pair.total_number_of_elements; + } + + inline size_type max_size() const noexcept { + return std::allocator_traits::max_size( *this ); + } + + inline size_type capacity() const noexcept { + return groups.element_allocator_pair.capacity; + } + + inline size_type approximate_memory_use() const noexcept { + return static_cast( sizeof( *this ) + ( groups.element_allocator_pair.capacity * sizeof( + node ) ) + ( sizeof( group ) * groups.group_allocator_pair.capacity ) ); + } + + private: + + struct less { + inline bool operator()( const element_type &a, const element_type &b ) const noexcept { + return a < b; + } + }; + + // Function-object to redirect the sort function to sort pointers by the elements they point to, not the pointer value + template + struct sort_dereferencer { + comparison_function stored_instance; + + explicit sort_dereferencer( const comparison_function &function_instance ): + stored_instance( function_instance ) + {} + + sort_dereferencer() noexcept + {} + + inline bool operator()( const node_pointer_type first, const node_pointer_type second ) { + return stored_instance( first->element, second->element ); + } + }; + + public: + + template + void sort( comparison_function compare ) { + if( node_pointer_allocator_pair.total_number_of_elements < 2 ) { + return; + } + + node_pointer_type *const node_pointers = LIST_ALLOCATE( node_pointer_allocator_type, + node_pointer_allocator_pair, node_pointer_allocator_pair.total_number_of_elements, NULL ); + node_pointer_type *node_pointer = node_pointers; + + // According to the C++ standard, construction of a pointer (of any type) may not trigger an exception - hence, no try-catch blocks are necessary for constructing the pointers: + for( group_pointer_type current_group = groups.block_pointer; + current_group != groups.last_endpoint_group; ++current_group ) { + const node_pointer_type end = current_group->beyond_end; + + if( ( end - current_group->nodes ) != + current_group->number_of_elements ) { // If there are erased nodes present in the group + for( node_pointer_type current_node = current_group->nodes; current_node != end; ++current_node ) { + if( current_node->next != NULL ) { // is not free list node + LIST_CONSTRUCT( node_pointer_allocator_type, node_pointer_allocator_pair, node_pointer++, + current_node ); + } + } + } else { + for( node_pointer_type current_node = current_group->nodes; current_node != end; ++current_node ) { + LIST_CONSTRUCT( node_pointer_allocator_type, node_pointer_allocator_pair, node_pointer++, + current_node ); + } + } + } + + if( ( last_endpoint - groups.last_endpoint_group->nodes ) != + groups.last_endpoint_group->number_of_elements ) { // If there are erased nodes present in the group + for( node_pointer_type current_node = groups.last_endpoint_group->nodes; + current_node != last_endpoint; ++current_node ) { + if( current_node->next != NULL ) { + LIST_CONSTRUCT( node_pointer_allocator_type, node_pointer_allocator_pair, node_pointer++, + current_node ); + } + } + } else { + for( node_pointer_type current_node = groups.last_endpoint_group->nodes; + current_node != last_endpoint; ++current_node ) { + LIST_CONSTRUCT( node_pointer_allocator_type, node_pointer_allocator_pair, node_pointer++, + current_node ); + } + } + + std::sort( node_pointers, node_pointers + node_pointer_allocator_pair.total_number_of_elements, + sort_dereferencer( compare ) ); + + begin_iterator.node_pointer = node_pointers[0]; + begin_iterator.node_pointer->next = node_pointers[1]; + begin_iterator.node_pointer->previous = end_iterator.node_pointer; + + end_node.next = node_pointers[0]; + end_node.previous = node_pointers[node_pointer_allocator_pair.total_number_of_elements - 1]; + end_node.previous->next = end_iterator.node_pointer; + end_node.previous->previous = node_pointers[node_pointer_allocator_pair.total_number_of_elements - + 2]; + + node_pointer_type *const back = node_pointers + + node_pointer_allocator_pair.total_number_of_elements - 1; + + for( node_pointer = node_pointers + 1; node_pointer != back; ++node_pointer ) { + ( *node_pointer )->next = *( node_pointer + 1 ); + ( *node_pointer )->previous = *( node_pointer - 1 ); + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( node_pointer_allocator_type, node_pointer_allocator_pair, node_pointer - 1 ); + } + } + + if LIST_CONSTEXPR( !std::is_trivially_destructible::value ) { + LIST_DESTROY( node_pointer_allocator_type, node_pointer_allocator_pair, back ); + } + + LIST_DEALLOCATE( node_pointer_allocator_type, node_pointer_allocator_pair, node_pointers, + node_pointer_allocator_pair.total_number_of_elements ); + } + + inline void sort() { + sort( less() ); + } + + void reorder( const iterator position, const iterator first, const iterator last ) noexcept { + last.node_pointer->next->previous = first.node_pointer->previous; + first.node_pointer->previous->next = last.node_pointer->next; + + last.node_pointer->next = position.node_pointer; + first.node_pointer->previous = position.node_pointer->previous; + + position.node_pointer->previous->next = first.node_pointer; + position.node_pointer->previous = last.node_pointer; + + if( begin_iterator == position ) { + begin_iterator = first; + } + } + + inline void reorder( const iterator position, const iterator location ) noexcept { + reorder( position, location, location ); + } + + void reserve( size_type reserve_amount ) { + if( reserve_amount == 0 || reserve_amount <= groups.element_allocator_pair.capacity ) { + return; + } else if( reserve_amount < LIST_BLOCK_MIN ) { + reserve_amount = LIST_BLOCK_MIN; + } else if( reserve_amount > max_size() ) { + reserve_amount = max_size(); + } + + if( groups.block_pointer != NULL && node_pointer_allocator_pair.total_number_of_elements == 0 ) { + // edge case: has been filled with elements then clear()'d - some groups may be smaller than would be desired, should be replaced + group_size_type end_group_size = static_cast( ( groups.block_pointer + groups.size + - 1 )->beyond_end - ( groups.block_pointer + groups.size - 1 )->nodes ); + + // if last group isn't large enough, remove all groups + if( reserve_amount > end_group_size && end_group_size != LIST_BLOCK_MAX ) { + reset(); + } else { + size_type number_of_full_groups_needed = reserve_amount / LIST_BLOCK_MAX; + group_size_type remainder = static_cast( reserve_amount - + ( number_of_full_groups_needed * LIST_BLOCK_MAX ) ); + + // Remove any max_size groups which're not needed and any groups that're smaller than remainder: + for( group_pointer_type current_group = groups.block_pointer; + current_group < groups.block_pointer + groups.size; ) { + const group_size_type current_group_size = static_cast + ( groups.block_pointer->beyond_end - groups.block_pointer->nodes ); + + if( number_of_full_groups_needed != 0 && current_group_size == LIST_BLOCK_MAX ) { + --number_of_full_groups_needed; + ++current_group; + } else if( remainder != 0 && current_group_size >= remainder ) { + remainder = 0; + ++current_group; + } else { + groups.remove( current_group ); + } + } + + last_endpoint = groups.block_pointer->nodes; + } + } + + reserve_amount -= groups.element_allocator_pair.capacity; + + // To correct from possible reallocation caused by add_new: + const difference_type last_endpoint_group_number = groups.last_endpoint_group - + groups.block_pointer; + + size_type number_of_full_groups = ( reserve_amount / LIST_BLOCK_MAX ); + reserve_amount -= ( number_of_full_groups++ * LIST_BLOCK_MAX ); // ++ to aid while loop below + + // Previously uninitialized list or reset in above if statement; most common scenario + if( groups.block_pointer == NULL ) { + if( reserve_amount != 0 ) { + groups.initialize( static_cast( ( ( reserve_amount < LIST_BLOCK_MIN ) ? + LIST_BLOCK_MIN : reserve_amount ) ) ); + } else { + groups.initialize( LIST_BLOCK_MAX ); + --number_of_full_groups; + } + } else if( reserve_amount != 0 ) { + // Create a group at least as large as the last group - may allocate more than necessary, but better solution than creating a veyr small group in the middle of the group vector, I think: + const group_size_type last_endpoint_group_capacity = static_cast + ( groups.last_endpoint_group->beyond_end - groups.last_endpoint_group->nodes ); + groups.add_new( static_cast( ( reserve_amount < last_endpoint_group_capacity ) ? + last_endpoint_group_capacity : reserve_amount ) ); + } + + while( --number_of_full_groups != 0 ) { + groups.add_new( LIST_BLOCK_MAX ); + } + + groups.last_endpoint_group = groups.block_pointer + last_endpoint_group_number; + } + + inline LIST_FORCE_INLINE void free_unused_memory() noexcept { + groups.trim_trailing_groups(); + } + + void shrink_to_fit() { + if( ( last_endpoint == NULL ) | ( node_pointer_allocator_pair.total_number_of_elements == + groups.element_allocator_pair.capacity ) ) { // uninitialized list or full + return; + } else if( node_pointer_allocator_pair.total_number_of_elements == 0 ) { // Edge case + reset(); + return; + } else if( node_allocator_pair.number_of_erased_nodes == 0 && + last_endpoint == + groups.last_endpoint_group->beyond_end ) { //edge case - currently no waste except for possible trailing groups + groups.trim_trailing_groups(); + return; + } + + list temp; + temp.reserve( node_pointer_allocator_pair.total_number_of_elements ); + + if LIST_CONSTEXPR( std::is_move_assignable::value && + std::is_move_constructible::value ) { // move elements if possible, otherwise copy them + temp.insert( temp.end_iterator, std::make_move_iterator( begin_iterator ), + std::make_move_iterator( end_iterator ) ); + } else { + temp.insert( temp.end_iterator, begin_iterator, end_iterator ); + } + + *this = std::move( temp ); + } + + private: + + void append_process( list &source ) { // used by merge and splice + if( last_endpoint != groups.last_endpoint_group->beyond_end ) { + // Add unused nodes to group's free list + const node_pointer_type back_node = last_endpoint - 1; + for( node_pointer_type current_node = groups.last_endpoint_group->beyond_end - 1; + current_node != back_node; --current_node ) { + current_node->next = NULL; + current_node->previous = groups.last_endpoint_group->free_list_head; + groups.last_endpoint_group->free_list_head = current_node; + } + + node_allocator_pair.number_of_erased_nodes += static_cast + ( groups.last_endpoint_group->beyond_end - last_endpoint ); + } + + groups.append( source.groups ); + last_endpoint = source.last_endpoint; + node_pointer_allocator_pair.total_number_of_elements += + source.node_pointer_allocator_pair.total_number_of_elements; + source.reset(); + } + + public: + + void splice( iterator position, list &source ) { + assert( &source != this ); + + if( source.node_pointer_allocator_pair.total_number_of_elements == 0 ) { + return; + } else if( node_pointer_allocator_pair.total_number_of_elements == 0 ) { + *this = std::move( source ); + return; + } + + if( position.node_pointer == + begin_iterator.node_pointer ) { // put source groups at front rather than back + swap( source ); + position.node_pointer = end_iterator.node_pointer; + } + + position.node_pointer->previous->next = source.begin_iterator.node_pointer; + source.begin_iterator.node_pointer->previous = position.node_pointer->previous; + position.node_pointer->previous = source.end_node.previous; + source.end_node.previous->next = position.node_pointer; + + append_process( source ); + } + + template + void merge( list &source, comparison_function compare ) { + assert( &source != this ); + splice( ( source.node_pointer_allocator_pair.total_number_of_elements >= + node_pointer_allocator_pair.total_number_of_elements ) ? end_iterator : begin_iterator, source ); + sort( compare ); + } + + void merge( list &source ) { + assert( &source != this ); + + if( source.node_pointer_allocator_pair.total_number_of_elements == 0 ) { + return; + } else if( node_pointer_allocator_pair.total_number_of_elements == 0 ) { + *this = std::move( source ); + return; + } + + node_pointer_type current1 = begin_iterator.node_pointer->next, + current2 = source.begin_iterator.node_pointer->next; + node_pointer_type previous = source.begin_iterator.node_pointer; + const node_pointer_type source_end = source.end_iterator.node_pointer, + this_end = end_iterator.node_pointer; + + begin_iterator.node_pointer->next = source.begin_iterator.node_pointer; + source.begin_iterator.node_pointer->previous = begin_iterator.node_pointer; + + + while( ( current1 != this_end ) & ( current2 != source_end ) ) { + previous->next = current1; + current1->previous = previous; + previous = current1; + current1 = current1->next; + + previous->next = current2; + current2->previous = previous; + previous = current2; + current2 = current2->next; + } + + if( current1 != this_end ) { + previous->next = current1; + current1->previous = previous; + } else { + end_node.previous = source.end_node.previous; + source.end_node.previous->next = end_iterator.node_pointer; + } + + append_process( source ); + } + + void reverse() noexcept { + if( node_pointer_allocator_pair.total_number_of_elements > 1 ) { + for( group_pointer_type current_group = groups.block_pointer; + current_group != groups.last_endpoint_group; ++current_group ) { + const node_pointer_type end = current_group->beyond_end; + + for( node_pointer_type current_node = current_group->nodes; current_node != end; ++current_node ) { + if( current_node->next != NULL ) { // is not free list node + // swap the pointers: + const node_pointer_type temp = current_node->next; + current_node->next = current_node->previous; + current_node->previous = temp; + } + } + } + + for( node_pointer_type current_node = groups.last_endpoint_group->nodes; + current_node != last_endpoint; ++current_node ) { + if( current_node->next != NULL ) { + const node_pointer_type temp = current_node->next; + current_node->next = current_node->previous; + current_node->previous = temp; + } + } + + const node_pointer_type temp = end_node.previous; + end_node.previous = begin_iterator.node_pointer; + begin_iterator.node_pointer = temp; + + end_node.previous->next = end_iterator.node_pointer; + begin_iterator.node_pointer->previous = end_iterator.node_pointer; + } + } + + private: + + // Used by unique() + struct eq { + inline bool operator()( const element_type &a, const element_type &b ) const noexcept { + return a == b; + } + }; + + // Used by remove() + struct eq_to { + const element_type value; + + explicit eq_to( const element_type store_value ): + value( store_value ) + {} + + eq_to() noexcept + {} + + inline bool operator()( const element_type compare_value ) const noexcept { + return value == compare_value; + } + }; + + public: + + template + size_type unique( comparison_function compare ) { + const size_type original_number_of_elements = node_pointer_allocator_pair.total_number_of_elements; + + if( original_number_of_elements > 1 ) { + element_type *previous = &( begin_iterator.node_pointer->element ); + + for( iterator current = ++iterator( begin_iterator ); current != end_iterator; ) { + if( compare( *current, *previous ) ) { + current = erase( current ); + } else { + previous = &( current++.node_pointer->element ); + } + } + } + + return original_number_of_elements - node_pointer_allocator_pair.total_number_of_elements; + } + + inline size_type unique() { + return unique( eq() ); + } + + template + size_type remove_if( predicate_function predicate ) { + const size_type original_number_of_elements = node_pointer_allocator_pair.total_number_of_elements; + + if( original_number_of_elements != 0 ) { + for( group_pointer_type current_group = groups.block_pointer; + current_group != groups.last_endpoint_group; ++current_group ) { + group_size_type num_elements = current_group->number_of_elements; + const node_pointer_type end = current_group->beyond_end; + + // If there are erased nodes present in the group + if( end - current_group->nodes != num_elements ) { + for( node_pointer_type current_node = current_group->nodes; current_node != end; ++current_node ) { + // is not free list node and validates predicate + if( current_node->next != NULL && predicate( current_node->element ) ) { + erase( current_node ); + + // ie. group will be empty (and removed) now - nothing left to iterate over + if( --num_elements == 0 ) { + // As current group has been removed, subsequent groups have already shifted back by one, hence, the ++ to the current group in the for loop is unnecessary, and negated here + --current_group; + break; + } + } + } + } else { // No erased nodes in group + for( node_pointer_type current_node = current_group->nodes; current_node != end; ++current_node ) { + if( predicate( current_node->element ) ) { + erase( current_node ); + + if( --num_elements == 0 ) { + --current_group; + break; + } + } + } + } + } + + group_size_type num_elements = groups.last_endpoint_group->number_of_elements; + + // If there are erased nodes present in the group + if( last_endpoint - groups.last_endpoint_group->nodes != num_elements ) { + for( node_pointer_type current_node = groups.last_endpoint_group->nodes; + current_node != last_endpoint; ++current_node ) { + if( current_node->next != NULL && predicate( current_node->element ) ) { + erase( current_node ); + + if( --num_elements == 0 ) { + break; + } + } + } + } else { + for( node_pointer_type current_node = groups.last_endpoint_group->nodes; + current_node != last_endpoint; ++current_node ) { + if( predicate( current_node->element ) ) { + erase( current_node ); + + if( --num_elements == 0 ) { + break; + } + } + } + } + } + + return original_number_of_elements - node_pointer_allocator_pair.total_number_of_elements; + } + + inline size_type remove( const element_type &value ) { + return remove_if( eq_to( value ) ); + } + + void resize( const size_type number_of_elements, const element_type &value = element_type() ) { + if( node_pointer_allocator_pair.total_number_of_elements == number_of_elements ) { + return; + } else if( number_of_elements == 0 ) { + clear(); + return; + } else if( node_pointer_allocator_pair.total_number_of_elements < number_of_elements ) { + insert( end_iterator, number_of_elements - node_pointer_allocator_pair.total_number_of_elements, + value ); + } else { // ie. node_pointer_allocator_pair.total_number_of_elements > number_of_elements + iterator current( end_node.previous ); + + for( size_type number_to_remove = node_pointer_allocator_pair.total_number_of_elements - + number_of_elements; number_to_remove != 0; --number_to_remove ) { + const node_pointer_type temp = current.node_pointer->previous; + erase( current ); + current.node_pointer = temp; + } + } + } + + // Range assign: + template + inline void assign( const typename plf_enable_if_c < + !std::numeric_limits::is_integer, iterator_type >::type first, + const iterator_type last ) { + clear(); + insert( end_iterator, first, last ); + groups.trim_trailing_groups(); + } + + // Fill assign: + inline void assign( const size_type number_of_elements, const element_type &value ) { + clear(); + reserve( number_of_elements ); // Will return anyway if capacity already > number_of_elements + insert( end_iterator, number_of_elements, value ); + } + + // Initializer-list assign: + inline void assign( const std::initializer_list &element_list ) { + clear(); + reserve( element_list.size() ); + insert( end_iterator, element_list ); + } + + inline allocator_type get_allocator() const noexcept { + return element_allocator_type(); + } + + void swap( list &source ) LIST_NOEXCEPT_SWAP( allocator_type ) { + list temp( std::move( source ) ); + source = std::move( *this ); + *this = std::move( temp ); + } +}; + +template +inline void swap( list &a, + list &b ) LIST_NOEXCEPT_SWAP( + swap_element_allocator_type ) +{ + a.swap( b ); +} + +} // namespace cata + +#undef LIST_BLOCK_MAX +#undef LIST_BLOCK_MIN + +#undef LIST_FORCE_INLINE + +#undef LIST_CONSTEXPR +#undef LIST_NOEXCEPT_SWAP +#undef LIST_NOEXCEPT_MOVE_ASSIGNMENT + +#undef LIST_CONSTRUCT +#undef LIST_DESTROY +#undef LIST_ALLOCATE +#undef LIST_ALLOCATE_INITIALIZATION +#undef LIST_DEALLOCATE + +#endif // LIST_H diff --git a/tests/colony_list_test_helpers.h b/tests/colony_list_test_helpers.h new file mode 100644 index 0000000000000..47644b9a63049 --- /dev/null +++ b/tests/colony_list_test_helpers.h @@ -0,0 +1,47 @@ +#pragma once +#ifndef COLONY_LIST_TEST_HELPERS_H +#define COLONY_LIST_TEST_HELPERS_H + +// Fast xorshift+128 random number generator function +// original: https://codingforspeed.com/using-faster-psudo-random-generator-xorshift/ +static unsigned int xor_rand() +{ + static unsigned int x = 123456789; + static unsigned int y = 362436069; + static unsigned int z = 521288629; + static unsigned int w = 88675123; + + const unsigned int t = x ^ ( x << 11 ); + + // Rotate the static values (w rotation in return statement): + x = y; + y = z; + z = w; + + return w = w ^ ( w >> 19 ) ^ ( t ^ ( t >> 8 ) ); +} + +struct small_struct { + double *empty_field_1; + double unused_number; + unsigned int empty_field2; + double *empty_field_3; + int number; + unsigned int empty_field4; + + small_struct( const int num ) noexcept: number( num ) {}; +}; + +struct perfect_forwarding_test { + const bool success; + + perfect_forwarding_test( int && /*perfect1*/, int &perfect2 ) : success( true ) { + perfect2 = 1; + } + + template + perfect_forwarding_test( T && /*imperfect1*/, U && /*imperfect2*/ ) : success( false ) + {} +}; + +#endif // COLONY_LIST_TEST_HELPERS_H diff --git a/tests/colony_test.cpp b/tests/colony_test.cpp index 54692288cd9ef..30a96008bfba5 100644 --- a/tests/colony_test.cpp +++ b/tests/colony_test.cpp @@ -6,25 +6,7 @@ #include "catch/catch.hpp" #include "colony.h" - -// Fast xorshift+128 random number generator function -// original: https://codingforspeed.com/using-faster-psudo-random-generator-xorshift/ -static unsigned int xor_rand() -{ - static unsigned int x = 123456789; - static unsigned int y = 362436069; - static unsigned int z = 521288629; - static unsigned int w = 88675123; - - const unsigned int t = x ^ ( x << 11 ); - - // Rotate the static values (w rotation in return statement): - x = y; - y = z; - z = w; - - return w = w ^ ( w >> 19 ) ^ ( t ^ ( t >> 8 ) ); -} +#include "colony_list_test_helpers.h" TEST_CASE( "colony basics", "[colony]" ) { @@ -267,7 +249,7 @@ TEST_CASE( "colony basics", "[colony]" ) CHECK( test_colony_2.max_size() > test_colony_2.size() ); } -TEST_CASE( "insert and erase", "[colony]" ) +TEST_CASE( "colony insert and erase", "[colony]" ) { cata::colony test_colony; @@ -504,7 +486,7 @@ TEST_CASE( "insert and erase", "[colony]" ) CHECK( test_colony.size() == static_cast( count ) ); } -TEST_CASE( "range erase", "[colony]" ) +TEST_CASE( "colony range erase", "[colony]" ) { cata::colony test_colony; @@ -763,7 +745,7 @@ TEST_CASE( "range erase", "[colony]" ) CHECK( test_colony.size() == 10 ); } -TEST_CASE( "sort", "[colony]" ) +TEST_CASE( "colony sort", "[colony]" ) { cata::colony test_colony; @@ -805,7 +787,7 @@ TEST_CASE( "sort", "[colony]" ) CHECK( sorted ); } -TEST_CASE( "insertion methods", "[colony]" ) +TEST_CASE( "colony insertion methods", "[colony]" ) { cata::colony test_colony = {1, 2, 3}; @@ -888,19 +870,7 @@ TEST_CASE( "insertion methods", "[colony]" ) CHECK( sum == 12060 ); } -struct perfect_forwarding_test { - const bool success; - - perfect_forwarding_test( int && /*perfect1*/, int &perfect2 ) : success( true ) { - perfect2 = 1; - } - - template - perfect_forwarding_test( T && /*imperfect1*/, U && /*imperfect2*/ ) : success( false ) - {} -}; - -TEST_CASE( "perfect forwarding", "[colony]" ) +TEST_CASE( "colony perfect forwarding", "[colony]" ) { cata::colony test_colony; @@ -913,19 +883,7 @@ TEST_CASE( "perfect forwarding", "[colony]" ) CHECK( lvalueref == 1 ); } -struct small_struct { - double *empty_field_1; - double unused_number; - unsigned int empty_field2; - double *empty_field_3; - int number; - unsigned int empty_field4; - - small_struct( const int num ) noexcept: - number( num ) {}; -}; - -TEST_CASE( "emplace", "[colony]" ) +TEST_CASE( "colony emplace", "[colony]" ) { cata::colony test_colony; int sum1 = 0, sum2 = 0; @@ -945,7 +903,7 @@ TEST_CASE( "emplace", "[colony]" ) CHECK( test_colony.size() == 100 ); } -TEST_CASE( "group size and capacity", "[colony]" ) +TEST_CASE( "colony group size and capacity", "[colony]" ) { cata::colony test_colony; test_colony.change_group_sizes( 50, 100 ); @@ -988,7 +946,7 @@ TEST_CASE( "group size and capacity", "[colony]" ) CHECK( test_colony.capacity() == 3400 ); } -TEST_CASE( "splice", "[colony]" ) +TEST_CASE( "colony splice", "[colony]" ) { cata::colony test_colony_1, test_colony_2; diff --git a/tests/list_test.cpp b/tests/list_test.cpp new file mode 100644 index 0000000000000..4890ddda09579 --- /dev/null +++ b/tests/list_test.cpp @@ -0,0 +1,1176 @@ +#include // std::find +#include // log redirection +#include // abort +#include // std::greater +#include // std::move +#include // range-insert testing + +#include "catch/catch.hpp" +#include "colony_list_test_helpers.h" +#include "list.h" + + +TEST_CASE( "list basics", "[list]" ) +{ + { + cata::list test_list; + int ten = 10; + int twenty = 20; + + SECTION( "empty()" ) { + CHECK( test_list.empty() ); + + test_list.push_front( &ten ); + + CHECK( !test_list.empty() ); + } + + SECTION( "begin() and end()" ) { + test_list.push_front( &ten ); + + CHECK( **test_list.begin() == 10 ); + CHECK_FALSE( test_list.begin() == test_list.end() ); + + test_list.clear(); + + CHECK( test_list.begin() == test_list.end() ); + } + + for( int i = 0; i < 200; i++ ) { + test_list.push_back( &ten ); + test_list.push_back( &twenty ); + } + + SECTION( "iterator count/access" ) { + int count = 0; + int sum = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); + ++it ) { + ++count; + sum += **it; + } + + CHECK( count == 400 ); + CHECK( sum == 6000 ); + } + + SECTION( "iterator distance" ) { + cata::list::iterator plus_twenty = test_list.begin(); + std::advance( plus_twenty, 20 ); + + cata::list::iterator plus_two_hundred = test_list.begin(); + std::advance( plus_two_hundred, 200 ); + + CHECK( std::distance( test_list.begin(), plus_twenty ) == 20 ); + CHECK( std::distance( test_list.begin(), plus_two_hundred ) == 200 ); + } + + SECTION( "iterator next/prev" ) { + cata::list::iterator next_iterator = std::next( test_list.begin(), 5 ); + cata::list::const_iterator prev_iterator = std::prev( test_list.cend(), 300 ); + + CHECK( std::distance( test_list.begin(), next_iterator ) == 5 ); + CHECK( std::distance( prev_iterator, test_list.cend() ) == 300 ); + } + + SECTION( "iterator/const_iterator equality" ) { + cata::list::const_iterator prev_iterator = std::prev( test_list.cend(), 300 ); + cata::list::iterator prev_iterator2 = std::prev( test_list.end(), 300 ); + + CHECK( prev_iterator == prev_iterator2 ); + } + + SECTION( "copy, equality, and inequality" ) { + cata::list test_list_2; + test_list_2 = test_list; + cata::list test_list_3( test_list ); + cata::list test_list_4( test_list_2, test_list_2.get_allocator() ); + + cata::list::iterator it1 = test_list.begin(); + cata::list::const_iterator cit( it1 ); + + CHECK( test_list_2.size() == 400 ); + CHECK( test_list_3.size() == 400 ); + CHECK( test_list_4.size() == 400 ); + + CHECK( test_list == test_list_2 ); + CHECK( test_list_2 == test_list_3 ); + + test_list_2.push_back( &ten ); + + CHECK( test_list_2 != test_list_3 ); + } + + SECTION( "reverse iterator count/access" ) { + int count = 0; + int sum = 0; + for( cata::list::reverse_iterator it = test_list.rbegin(); + it != test_list.rend(); ++it ) { + ++count; + sum += **it; + } + + CHECK( count == 400 ); + CHECK( sum == 6000 ); + } + + SECTION( "reverse iterator advance, next, and distance" ) { + cata::list::reverse_iterator r_iterator = test_list.rbegin(); + std::advance( r_iterator, 50 ); + + CHECK( std::distance( test_list.rbegin(), r_iterator ) == 50 ); + + cata::list::reverse_iterator r_iterator2 = std::next( r_iterator, 2 ); + + CHECK( std::distance( test_list.rbegin(), r_iterator2 ) == 52 ); + } + + + SECTION( "multiple iteration" ) { + int count = 0; + int sum = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); + std::advance( it, 2 ) ) { + ++count; + sum += **it; + } + + CHECK( count == 200 ); + CHECK( sum == 2000 ); + } + + SECTION( "reverse iterator count/access" ) { + int count = 0; + int sum = 0; + for( cata::list::const_iterator it = test_list.cbegin(); + it != test_list.cend(); ++it ) { + ++count; + sum += **it; + } + + CHECK( count == 400 ); + CHECK( sum == 6000 ); + } + + SECTION( "reverse iterator count/access" ) { + int count = 0; + int sum = 0; + for( cata::list::const_reverse_iterator it = test_list.crbegin(); + it != test_list.crend(); ++it ) { + ++count; + sum += **it; + } + + CHECK( count == 400 ); + CHECK( sum == 6000 ); + } + + SECTION( "post erase iteration and shrink to fit" ) { + int count = 0; + for( cata::list::iterator it = std::next( test_list.begin() ); + it != test_list.end(); ++it ) { + ++count; + it = test_list.erase( it ); + + if( it == test_list.end() ) { + break; + } + } + + CHECK( count == 200 ); + CHECK( test_list.size() == 200 ); + + const size_t prev_capacity = test_list.capacity(); + test_list.shrink_to_fit(); + + CHECK( test_list.capacity() < prev_capacity ); + CHECK( test_list.capacity() == 200 ); + + count = 0; + for( cata::list::reverse_iterator it = test_list.rbegin(); + it != test_list.rend(); ) { + ++it; + cata::list::iterator it2 = it.base(); // grabs it--, essentially + test_list.erase( it2 ); + ++count; + } + + CHECK( count == 200 ); + CHECK( test_list.size() == 0 ); + } + + SECTION( "negative iteration" ) { + int count = 0; + for( cata::list::iterator it = std::prev( test_list.end() ); + it != test_list.begin(); --it ) { + ++count; + } + + CHECK( count == 399 ); + } + + SECTION( "negative multiple iteration" ) { + int count = 0; + for( cata::list::iterator it = std::prev( test_list.end() ); + it != test_list.begin() && + it != std::next( test_list.begin() ); std::advance( it, -2 ) ) { + ++count; + } + + CHECK( count == 199 ); + } + + SECTION( "move" ) { + cata::list test_list_2; + test_list_2 = std::move( test_list ); + + CHECK( test_list_2.size() == 400 ); + + cata::list test_list_3( test_list_2 ); + cata::list test_list_4( std::move( test_list_3 ), test_list_2.get_allocator() ); + + CHECK( test_list_4.size() == 400 ); + } + + SECTION( "swap() and max_size()" ) { + cata::list test_list_2; + test_list_2 = test_list; + + CHECK( test_list_2.size() == 400 ); + + test_list.push_back( &ten ); + + test_list.swap( test_list_2 ); + + CHECK( test_list.size() == test_list_2.size() - 1 ); + + swap( test_list, test_list_2 ); + + CHECK( test_list_2.size() == test_list.size() - 1 ); + + CHECK( test_list_2.max_size() > test_list_2.size() ); + } + } +} + +TEST_CASE( "list insert and erase", "[list]" ) +{ + cata::list test_list; + + for( int i = 0; i < 500000; i++ ) { + test_list.push_back( i ); + } + + SECTION( "size after insert" ) { + CHECK( test_list.size() == 500000 ); + } + + SECTION( "find iterator" ) { + cata::list::iterator found_item = std::find( test_list.begin(), test_list.end(), 5000 ); + + CHECK( *found_item == 5000 ); + } + + SECTION( "find reverse iterator" ) { + cata::list::reverse_iterator found_item2 = std::find( test_list.rbegin(), test_list.rend(), + 5000 ); + + CHECK( *found_item2 == 5000 ); + } + + SECTION( "erase alternating/randomly" ) { + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); + ++it ) { + it = test_list.erase( it ); + } + + CHECK( test_list.size() == 250000 ); + + do { + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ) { + if( ( xor_rand() & 7 ) == 0 ) { + it = test_list.erase( it ); + } else { + ++it; + } + } + + } while( !test_list.empty() ); + + CHECK( test_list.size() == 0 ); + } + + SECTION( "erase randomly till half empty" ) { + int count = 0; + do { + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ) { + if( ( xor_rand() & 7 ) == 0 ) { + it = test_list.erase( it ); + ++count; + } else { + ++it; + } + } + + } while( count < 250000 ); + + CHECK( test_list.size() == 500000 - count ); + + for( int i = 0; i < count; i++ ) { + test_list.push_front( 1 ); + } + + CHECK( test_list.size() == 500000 ); + } + + SECTION( "alternating insert/erase" ) { + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ) { + if( ++count == 5 ) { + count = 0; + it = test_list.erase( it ); + } else { + test_list.insert( it, 1 ); + ++it; + } + } + + CHECK( test_list.size() == 800000 ); + } + + test_list.clear(); + for( int i = 0; i < 500000; i++ ) { + test_list.push_back( 1 ); + } + + SECTION( "large multi increment erasure" ) { + cata::list::iterator it = test_list.begin(); + std::advance( it, 250000 ); + + for( ; it != test_list.end(); ) { + it = test_list.erase( it ); + } + + CHECK( test_list.size() == 250000 ); + + SECTION( "re-insert post heavy erasure" ) { + for( int i = 0; i < 250000; i++ ) { + test_list.push_front( 1 ); + } + + int sum = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); + ++it ) { + sum += *it; + } + + CHECK( sum == 500000 ); + } + } + + SECTION( "large multi decrement erasure" ) { + cata::list::iterator end_iterator = test_list.end(); + std::advance( end_iterator, -250000 ); + + for( cata::list::iterator it = test_list.begin(); it != end_iterator; ) { + it = test_list.erase( it ); + } + + CHECK( test_list.size() == 250000 ); + + SECTION( "re-insert post heavy erasure" ) { + for( int i = 0; i < 250000; i++ ) { + test_list.push_front( 1 ); + } + + int sum = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); + ++it ) { + sum += *it; + } + + CHECK( sum == 500000 ); + } + } + + SECTION( "erase from middle" ) { + cata::list::iterator end_iterator = test_list.end(); + std::advance( end_iterator, -50001 ); + cata::list::iterator begin_iterator = test_list.begin(); + std::advance( begin_iterator, 300000 ); + + for( cata::list::iterator it = begin_iterator; it != end_iterator; ) { + it = test_list.erase( it ); + } + + CHECK( test_list.size() == 350001 ); + } + + SECTION( "total erase edge case" ) { + cata::list::iterator temp_iterator = test_list.begin(); + std::advance( temp_iterator, 2 ); // Advance test 1 + + test_list.erase( temp_iterator ); + // Check edge-case with advance when erasures present in initial group + temp_iterator = test_list.begin(); + std::advance( temp_iterator, 500 ); + + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ) { + it = test_list.erase( it ); + } + + CHECK( test_list.empty() ); + } + + SECTION( "multiple sequential small insert/erase" ) { + test_list.clear(); + test_list.shrink_to_fit(); + + const size_t prev_capacity = test_list.capacity(); + test_list.reserve( 1000 ); + + CHECK( prev_capacity != test_list.capacity() ); + CHECK( test_list.capacity() == 1000 ); + + int count = 0; + for( int loop1 = 0; loop1 < 50000; loop1++ ) { + for( int loop = 0; loop < 10; loop++ ) { + if( ( xor_rand() & 7 ) == 0 ) { + test_list.push_back( 1 ); + ++count; + } + } + + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ) { + if( ( xor_rand() & 7 ) == 0 ) { + it = test_list.erase( it ); + --count; + } else { + ++it; + } + } + } + + CHECK( count == test_list.size() ); + } +} + +TEST_CASE( "list merge", "[list]" ) +{ + cata::list test_list; + test_list.insert( test_list.end(), {1, 3, 5, 7, 9} ); + cata::list test_list_2 = {2, 4, 6, 8, 10}; + + test_list.merge( test_list_2 ); + + bool passed = true; + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( ++count != *it ) { + passed = false; + break; + } + } + + CHECK( passed ); +} + +TEST_CASE( "list splice", "[list]" ) +{ + cata::list test_list = {1, 2, 3, 4, 5}; + cata::list test_list_2 = {6, 7, 8, 9, 10}; + + SECTION( "splice at end" ) { + test_list.splice( test_list.end(), test_list_2 ); + + bool passed = true; + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( ++count != *it ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + SECTION( "splice at begin" ) { + test_list.splice( test_list.begin(), test_list_2 ); + + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + count += *it; + } + + CHECK( count == 55 ); + } + + SECTION( "splice past middle" ) { + cata::list::iterator it2 = test_list.begin(); + std::advance( it2, 3 ); + + test_list.splice( it2, test_list_2 ); + + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + count += *it; + } + + test_list.clear(); + test_list_2.clear(); + + for( int i = 1; i < 25; i++ ) { + test_list.push_back( i ); + test_list_2.push_back( i + 25 ); + } + + cata::list::iterator it3 = test_list.begin(); + std::advance( it3, 18 ); + + test_list.splice( it3, test_list_2 ); + + count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + count += *it; + } + + CHECK( count == 1200 ); + } + + SECTION( "large splice" ) { + test_list.clear(); + test_list_2.clear(); + + for( int i = 5; i < 36; i++ ) { + test_list.push_back( i ); + test_list_2.push_front( i ); + } + + + test_list.splice( test_list.begin(), test_list_2 ); + + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + count += *it; + } + + CHECK( count == 1240 ); + } +} + +TEST_CASE( "list sort and reverse", "[list]" ) +{ + cata::list test_list; + + for( int i = 0; i < 500; ++i ) { + test_list.push_back( xor_rand() & 65535 ); + } + + SECTION( "less than (default)" ) { + test_list.sort(); + + bool passed = true; + int previous = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( *it < previous ) { + passed = false; + break; + } + previous = *it; + } + + CHECK( passed ); + } + + SECTION( "greater than (predicate)" ) { + test_list.sort( std::greater() ); + + bool passed = true; + int previous = 65535; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( *it > previous ) { + passed = false; + break; + } + previous = *it; + } + + CHECK( passed ); + + SECTION( "reverse" ) { + test_list.reverse(); + + passed = true; + previous = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + + if( *it < previous ) { + passed = false; + break; + } + + previous = *it; + } + + CHECK( passed ); + } + } +} + +TEST_CASE( "list unique", "[list]" ) +{ + cata::list test_list = {1, 1, 2, 3, 3, 4, 5, 5}; + + SECTION( "control case" ) { + bool passed = true; + int previous = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( *it == previous ) { + passed = false; + } + + previous = *it; + } + + CHECK_FALSE( passed ); + } + + SECTION( "invoke unique" ) { + test_list.unique(); + + bool passed = true; + int previous = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( *it == previous ) { + passed = false; + break; + } + + previous = *it; + } + + CHECK( passed ); + } +} + +TEST_CASE( "list remove", "[list]" ) +{ + cata::list test_list = {1, 3, 1, 50, 16, 15, 2, 22}; + + SECTION( "remove_if()" ) { + test_list.remove_if( []( int value ) { + return value > 15; + } ); + + bool passed = true; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( *it > 15 ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + SECTION( "remove()" ) { + test_list.remove( 1 ); + + bool passed = true; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( *it == 1 ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + SECTION( "remove() till empty" ) { + test_list.remove( 1 ); + test_list.remove( 22 ); + test_list.remove( 15 ); + test_list.remove( 16 ); + test_list.remove( 3 ); + test_list.remove( 50 ); + test_list.remove( 2 ); + + CHECK( test_list.empty() ); + } +} + +TEST_CASE( "list reserve", "[list]" ) +{ + cata::list test_list; + + test_list.reserve( 4097 ); + + CHECK( test_list.capacity() >= 4097 ); + + test_list.push_back( 15 ); + + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + ++count; + } + + CHECK( test_list.size() == static_cast( count ) ); + + test_list.insert( test_list.end(), 10000, 15 ); + + count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + ++count; + } + + CHECK( test_list.size() == 10001 ); + CHECK( count == 10001 ); + CHECK( test_list.capacity() >= 10001 ); + + test_list.reserve( 15000 ); + + CHECK( test_list.capacity() >= 15000 ); +} + +TEST_CASE( "list resize", "[list]" ) +{ + cata::list test_list = { 1, 2, 3, 4, 5, 6, 7 }; + + test_list.resize( 2 ); + + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + ++count; + } + + CHECK( test_list.size() == 2 ); + CHECK( count == 2 ); +} + +TEST_CASE( "list assign", "[list]" ) +{ + cata::list test_list; + + SECTION( "range assign" ) { + std::vector test_vector = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + + test_list.assign( test_vector.begin(), test_vector.end() ); + + bool passed = true; + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( ++count != *it ) { + passed = false; + break; + } + } + + CHECK( test_list.size() == 10 ); + CHECK( count == 10 ); + CHECK( passed ); + } + + SECTION( "fill assign" ) { + test_list.assign( 20, 1 ); + + bool passed = true; + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( *it != 1 ) { + passed = false; + break; + } + ++count; + } + + CHECK( test_list.size() == 20 ); + CHECK( count == 20 ); + CHECK( passed ); + } + + SECTION( "initializer list assign" ) { + std::initializer_list inlist = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}; + + test_list.assign( inlist ); + + bool passed = true; + int count = 11; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( --count != *it ) { + passed = false; + break; + } + } + + CHECK( test_list.size() == 10 ); + CHECK( count == 1 ); + CHECK( passed ); + } +} + +TEST_CASE( "list insert", "[list]" ) +{ + cata::list test_list; + + std::vector test_vector = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + test_list.insert( test_list.end(), test_vector.begin(), test_vector.end() ); + + SECTION( "range insert" ) { + + bool passed = true; + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( ++count != *it ) { + passed = false; + } + } + + CHECK( passed ); + } + + test_list.insert( test_list.begin(), 50, 50000 ); + + SECTION( "fill insert" ) { + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + ++count; + } + + CHECK( count == 60 ); + CHECK( test_list.size() == 60 ); + } + + SECTION( "erase/insert randomly til empty" ) { + while( !test_list.empty() ) { + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ) { + if( ( xor_rand() & 15 ) == 0 ) { + test_list.insert( it, 13 ); + } + + if( ( xor_rand() & 7 ) == 0 ) { + it = test_list.erase( it ); + } else { + ++it; + } + } + } + + CHECK( test_list.empty() ); + } +} + +TEST_CASE( "list emplace, move, copy, and reverse iterate", "[list]" ) +{ + cata::list test_list; + + for( int counter = 0; counter < 254; counter++ ) { + test_list.emplace_back( counter ); + } + + SECTION( "emplace_back() success" ) { + bool passed = true; + int count = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( count++ != it->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + SECTION( "emplace_back() return value" ) { + small_struct &temp = test_list.emplace_back( 254 ); + CHECK( temp.number == 254 ); + } + + SECTION( "reverse iteration" ) { + bool passed = true; + int count = 254; + for( cata::list::reverse_iterator rit = test_list.rbegin(); + rit != test_list.rend(); ++rit ) { + if( --count != rit->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + for( int counter = -1; counter != -255; --counter ) { + test_list.emplace_front( counter ); + } + + SECTION( "emplace_front()" ) { + bool passed = true; + int count = -255; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + if( ++count != it->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + SECTION( "emplace_front() return value" ) { + small_struct &temp = test_list.emplace_front( -255 ); + CHECK( temp.number == -255 ); + } + + SECTION( "reverse iteration 2" ) { + bool passed = true; + int count = 254; + for( cata::list::reverse_iterator rit = test_list.rbegin(); + rit != test_list.rend(); ++rit ) { + if( --count != rit->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + cata::list test_list_2( std::move( test_list ) ); + + SECTION( "move constructor" ) { + bool passed = true; + int count = 254; + for( cata::list::reverse_iterator rit = test_list_2.rbegin(); + rit != test_list_2.rend(); ++rit ) { + if( --count != rit->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + CHECK( test_list.empty() ); + } + + SECTION( "emplace post-moved list" ) { + // Reuse the moved list will cause segmentation fault + test_list.emplace_back( 3 ); + CHECK( test_list.size() == 1 ); + } + + test_list = std::move( test_list_2 ); + + SECTION( "move assignment" ) { + bool passed = true; + int count = 254; + for( cata::list::reverse_iterator rit = test_list.rbegin(); + rit != test_list.rend(); ++rit ) { + if( --count != rit->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + CHECK( test_list_2.empty() ); + } + + SECTION( "copy assignment" ) { + test_list_2 = test_list; + + bool passed = true; + int count = 254; + for( cata::list::reverse_iterator rit = test_list_2.rbegin(); + rit != test_list_2.rend(); + ++rit ) { + if( --count != rit->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + } + + SECTION( "copy constructor" ) { + cata::list list3( test_list ); + + bool passed = true; + int count = 254; + for( cata::list::reverse_iterator rit = list3.rbegin(); + rit != list3.rend(); ++rit ) { + if( --count != rit->number ) { + passed = false; + break; + } + } + + CHECK( passed ); + } +} + +TEST_CASE( "list reorder", "[list]" ) +{ + cata::list test_list; + + for( int i = 0; i < 255; ++i ) { + test_list.push_back( i ); + } + + // Used for the post reorder data consistency test + int original_sum = 0; + for( cata::list::iterator it = test_list.begin(); it != test_list.end(); ++it ) { + original_sum += *it; + } + + cata::list::iterator it1 = test_list.begin(); + cata::list::iterator it2 = test_list.begin(); + cata::list::iterator it3 = test_list.begin(); + + std::advance( it1, 25 ); + std::advance( it2, 5 ); + + test_list.reorder( it2, it1 ); + + it1 = test_list.begin(); + std::advance( it1, 5 ); + + SECTION( "single reorder" ) { + CHECK( *it1 == 25 ); + } + + + it1 = test_list.begin(); + std::advance( it1, 152 ); + + test_list.reorder( test_list.begin(), it1 ); + + SECTION( "single reorder to begin" ) { + CHECK( test_list.front() == 152 ); + } + + test_list.reorder( test_list.end(), it2 ); + + SECTION( "single reorder to end" ) { + it1 = std::prev( test_list.end() ); + CHECK( *it1 == 5 ); + } + + it1 = test_list.begin(); + it2 = test_list.begin(); + + std::advance( it1, 50 ); + std::advance( it2, 60 ); + std::advance( it3, 70 ); + + test_list.reorder( it3, it1, it2 ); + + SECTION( "range reorder" ) { + it3 = test_list.begin(); + std::advance( it3, 60 ); + + bool passed = true; + for( int test = 50; test < 60; test++ ) { + if( *it3 != test ) { + passed = false; + } + ++it3; + } + + CHECK( passed == true ); + } + + it1 = test_list.begin(); + it2 = test_list.begin(); + + std::advance( it1, 80 ); + std::advance( it2, 120 ); + + test_list.reorder( test_list.end(), it1, it2 ); + + SECTION( "range reorer to end" ) { + it3 = test_list.end(); + std::advance( it3, -41 ); + + bool passed = true; + for( int test = 80; test < 120; test++ ) { + if( *it3 != test ) { + passed = false; + } + ++it3; + } + + CHECK( passed == true ); + } + + it1 = test_list.begin(); + it2 = test_list.begin(); + + std::advance( it1, 40 ); + std::advance( it2, 45 ); + + test_list.reorder( test_list.begin(), it1, it2 ); + + SECTION( "range reorder to begin" ) { + it3 = test_list.begin(); + + bool passed = true; + for( int test = 40; test < 45; test++ ) { + if( *it3 != test ) { + passed = false; + } + ++it3; + } + + CHECK( passed == true ); + } + + SECTION( "post reorder data consistency" ) { + int sum = 0; + for( it1 = test_list.begin(); it1 != test_list.end(); ++it1 ) { + sum += *it1; + } + + CHECK( sum == original_sum ); + } +} + +TEST_CASE( "list insertion styles", "[list]" ) +{ + cata::list test_list = {1, 2, 3}; + + CHECK( test_list.size() == 3 ); + + cata::list test_list_2( test_list.begin(), test_list.end() ); + + CHECK( test_list_2.size() == 3 ); + + cata::list test_list_3( 5000, 2 ); + + CHECK( test_list_3.size() == 5000 ); + + test_list_2.insert( test_list_2.end(), 500000, 5 ); + + CHECK( test_list_2.size() == 500003 ); + + std::vector some_ints( 500, 2 ); + + test_list_2.insert( test_list_2.begin(), some_ints.begin(), some_ints.end() ); + + CHECK( test_list_2.size() == 500503 ); +} + +TEST_CASE( "list perfect forwarding", "[list]" ) +{ + cata::list test_list; + + int lvalue = 0; + int &lvalueref = lvalue; + + test_list.emplace( test_list.end(), 7, lvalueref ); + + CHECK( ( *test_list.begin() ).success ); + CHECK( lvalueref == 1 ); +}