#ifndef ABSL_CONTAINER_CHUNKED_QUEUE_H_
#define ABSL_CONTAINER_CHUNKED_QUEUE_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <iterator>
#include <memory>
#include <new>
#include <tuple>
#include <type_traits>
#include <utility>
#include "absl/base/attributes.h"
#include "absl/base/config.h"
#include "absl/base/internal/iterator_traits.h"
#include "absl/base/macros.h"
#include "absl/container/internal/chunked_queue.h"
#include "absl/container/internal/layout.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
template <typename T, size_t BLo = 0, size_t BHi = BLo,
typename Allocator = std::allocator<T>>
class chunked_queue {
public:
static constexpr size_t kBlockSizeMin = (BLo == 0 && BHi == 0) ? 1 : BLo;
static constexpr size_t kBlockSizeMax = (BLo == 0 && BHi == 0) ? 128 : BHi;
private:
static_assert(kBlockSizeMin > 0, "Min block size cannot be zero");
static_assert(kBlockSizeMin <= kBlockSizeMax, "Invalid block size bounds");
using Block = container_internal::ChunkedQueueBlock<T, Allocator>;
using AllocatorTraits = std::allocator_traits<Allocator>;
class iterator_common {
public:
friend bool operator==(const iterator_common& a, const iterator_common& b) {
return a.ptr == b.ptr;
}
friend bool operator!=(const iterator_common& a, const iterator_common& b) {
return !(a == b);
}
protected:
iterator_common() = default;
explicit iterator_common(Block* b)
: block(b), ptr(b->start()), limit(b->limit()) {}
void Incr() {
++ptr;
if (ptr == limit && block->next()) *this = iterator_common(block->next());
}
void IncrBy(size_t n) {
while (ptr + n > limit) {
n -= limit - ptr;
*this = iterator_common(block->next());
}
ptr += n;
}
Block* block = nullptr;
T* ptr = nullptr;
T* limit = nullptr;
};
template <typename CT>
class basic_iterator : public iterator_common {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = typename AllocatorTraits::value_type;
using difference_type = typename AllocatorTraits::difference_type;
using pointer =
typename std::conditional<std::is_const<CT>::value,
typename AllocatorTraits::const_pointer,
typename AllocatorTraits::pointer>::type;
using reference = CT&;
basic_iterator() = default;
basic_iterator(const basic_iterator<T>& it) : iterator_common(it) {}
basic_iterator& operator=(const basic_iterator& other) = default;
reference operator*() const { return *this->ptr; }
pointer operator->() const { return this->ptr; }
basic_iterator& operator++() {
this->Incr();
return *this;
}
basic_iterator operator++(int) {
basic_iterator t = *this;
++*this;
return t;
}
private:
explicit basic_iterator(Block* b) : iterator_common(b) {}
friend chunked_queue;
};
public:
using allocator_type = typename AllocatorTraits::allocator_type;
using value_type = typename AllocatorTraits::value_type;
using size_type = typename AllocatorTraits::size_type;
using difference_type = typename AllocatorTraits::difference_type;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = basic_iterator<T>;
using const_iterator = basic_iterator<const T>;
chunked_queue() : chunked_queue(allocator_type()) {}
explicit chunked_queue(const allocator_type& alloc)
: alloc_and_size_(alloc) {}
explicit chunked_queue(size_type count,
const allocator_type& alloc = allocator_type())
: alloc_and_size_(alloc) {
resize(count);
}
chunked_queue(size_type count, const T& value,
const allocator_type& alloc = allocator_type())
: alloc_and_size_(alloc) {
assign(count, value);
}
template <typename Iter,
typename = std::enable_if_t<
base_internal::IsAtLeastInputIterator<Iter>::value>>
chunked_queue(Iter first, Iter last,
const allocator_type& alloc = allocator_type())
: alloc_and_size_(alloc) {
using Tag = typename std::iterator_traits<Iter>::iterator_category;
RangeInit(first, last, Tag());
}
chunked_queue(std::initializer_list<T> list,
const allocator_type& alloc = allocator_type())
: chunked_queue(list.begin(), list.end(), alloc) {}
~chunked_queue();
chunked_queue(const chunked_queue& other)
: chunked_queue(other,
AllocatorTraits::select_on_container_copy_construction(
other.alloc_and_size_.allocator())) {}
chunked_queue(const chunked_queue& other, const allocator_type& alloc)
: alloc_and_size_(alloc) {
for (const_reference item : other) {
push_back(item);
}
}
chunked_queue(chunked_queue&& other) noexcept
: head_(other.head_),
tail_(other.tail_),
alloc_and_size_(std::move(other.alloc_and_size_)) {
other.head_ = {};
other.tail_ = {};
other.alloc_and_size_.size = 0;
}
chunked_queue& operator=(std::initializer_list<T> il) {
assign(il.begin(), il.end());
return *this;
}
chunked_queue& operator=(const chunked_queue& other) {
if (this == &other) {
return *this;
}
if (AllocatorTraits::propagate_on_container_copy_assignment::value &&
(alloc_and_size_.allocator() != other.alloc_and_size_.allocator())) {
DestroyAndDeallocateAll();
alloc_and_size_ = AllocatorAndSize(other.alloc_and_size_.allocator());
}
assign(other.begin(), other.end());
return *this;
}
chunked_queue& operator=(chunked_queue&& other) noexcept;
bool empty() const { return alloc_and_size_.size == 0; }
size_t size() const { return alloc_and_size_.size; }
size_type max_size() const noexcept {
return AllocatorTraits::max_size(alloc_and_size_.allocator());
}
void resize(size_t new_size);
void resize(size_type new_size, const T& value) {
if (new_size > size()) {
size_t to_add = new_size - size();
for (size_t i = 0; i < to_add; ++i) {
push_back(value);
}
} else {
resize(new_size);
}
}
void shrink_to_fit() {
if (empty()) {
chunked_queue(alloc_and_size_.allocator()).swap(*this);
}
}
template <typename Iter,
typename = std::enable_if_t<
base_internal::IsAtLeastInputIterator<Iter>::value>>
void assign(Iter first, Iter last) {
auto out = begin();
Block* prev_block = nullptr;
for (; out != end() && first != last; ++first) {
if (out.ptr + 1 == out.block->limit()) {
prev_block = out.block;
}
*out = *first;
++out;
}
if (!empty() && out.block != nullptr && out.ptr == out.block->start() &&
prev_block != nullptr) {
iterator prev_block_end(prev_block);
prev_block_end.ptr = prev_block->limit();
EraseAllFrom(prev_block_end);
tail_ = prev_block_end;
prev_block->set_next(nullptr);
} else {
EraseAllFrom(out);
}
for (; first != last; ++first) {
push_back(*first);
}
}
void assign(size_type count, const T& value) {
clear();
for (size_type i = 0; i < count; ++i) {
push_back(value);
}
}
void assign(std::initializer_list<T> il) { assign(il.begin(), il.end()); }
void push_back(const T& val) { emplace_back(val); }
void push_back(T&& val) { emplace_back(std::move(val)); }
template <typename... A>
T& emplace_back(A&&... args) {
T* storage = AllocateBack();
AllocatorTraits::construct(alloc_and_size_.allocator(), storage,
std::forward<A>(args)...);
return *storage;
}
void pop_front();
T& front() {
ABSL_HARDENING_ASSERT(!empty());
return *head_;
}
const T& front() const {
ABSL_HARDENING_ASSERT(!empty());
return *head_;
}
T& back() {
ABSL_HARDENING_ASSERT(!empty());
return *(&*tail_ - 1);
}
const T& back() const {
ABSL_HARDENING_ASSERT(!empty());
return *(&*tail_ - 1);
}
void swap(chunked_queue& other) noexcept {
using std::swap;
swap(head_, other.head_);
swap(tail_, other.tail_);
if (AllocatorTraits::propagate_on_container_swap::value) {
swap(alloc_and_size_, other.alloc_and_size_);
} else {
ABSL_HARDENING_ASSERT(get_allocator() == other.get_allocator());
swap(alloc_and_size_.size, other.alloc_and_size_.size);
}
}
void clear();
iterator begin() { return head_; }
iterator end() { return tail_; }
const_iterator begin() const { return head_; }
const_iterator end() const { return tail_; }
const_iterator cbegin() const { return head_; }
const_iterator cend() const { return tail_; }
allocator_type get_allocator() const { return alloc_and_size_.allocator(); }
private:
struct AllocatorAndSize : private allocator_type {
explicit AllocatorAndSize(const allocator_type& alloc)
: allocator_type(alloc) {}
const allocator_type& allocator() const { return *this; }
allocator_type& allocator() { return *this; }
size_t size = 0;
};
template <typename Iter>
void RangeInit(Iter first, Iter last, std::input_iterator_tag) {
while (first != last) {
AddTailBlock();
for (; first != last && tail_.ptr != tail_.limit;
++alloc_and_size_.size, ++tail_.ptr, ++first) {
AllocatorTraits::construct(alloc_and_size_.allocator(), tail_.ptr,
*first);
}
}
}
void Construct(T* start, T* limit) {
ABSL_ASSERT(start <= limit);
for (; start != limit; ++start) {
AllocatorTraits::construct(alloc_and_size_.allocator(), start);
}
}
size_t Destroy(T* start, T* limit) {
ABSL_ASSERT(start <= limit);
const size_t n = limit - start;
for (; start != limit; ++start) {
AllocatorTraits::destroy(alloc_and_size_.allocator(), start);
}
return n;
}
T* block_begin(Block* b) const {
return b == head_.block ? head_.ptr : b->start();
}
T* block_end(Block* b) const {
return !b->next() ? tail_.ptr : b->limit();
}
void AddTailBlock();
size_t NewBlockSize() {
if (!tail_.block) return kBlockSizeMin;
return (std::min)(kBlockSizeMax, 2 * tail_.block->size());
}
T* AllocateBack();
void EraseAllFrom(iterator i);
void DestroyAndDeallocateAll();
iterator head_;
iterator tail_;
AllocatorAndSize alloc_and_size_;
};
template <typename T, size_t BLo, size_t BHi, typename Allocator>
constexpr size_t chunked_queue<T, BLo, BHi, Allocator>::kBlockSizeMin;
template <typename T, size_t BLo, size_t BHi, typename Allocator>
constexpr size_t chunked_queue<T, BLo, BHi, Allocator>::kBlockSizeMax;
template <typename T, size_t BLo, size_t BHi, typename Allocator>
inline void swap(chunked_queue<T, BLo, BHi, Allocator>& a,
chunked_queue<T, BLo, BHi, Allocator>& b) noexcept {
a.swap(b);
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
chunked_queue<T, BLo, BHi, Allocator>&
chunked_queue<T, BLo, BHi, Allocator>::operator=(
chunked_queue&& other) noexcept {
if (this == &other) {
return *this;
}
DestroyAndDeallocateAll();
if constexpr (AllocatorTraits::propagate_on_container_move_assignment::
value) {
head_ = other.head_;
tail_ = other.tail_;
alloc_and_size_ = std::move(other.alloc_and_size_);
other.head_ = {};
other.tail_ = {};
other.alloc_and_size_.size = 0;
} else if (get_allocator() == other.get_allocator()) {
head_ = other.head_;
tail_ = other.tail_;
alloc_and_size_.size = other.alloc_and_size_.size;
other.head_ = {};
other.tail_ = {};
other.alloc_and_size_.size = 0;
} else {
for (auto& elem : other) {
push_back(std::move(elem));
}
}
return *this;
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
inline chunked_queue<T, BLo, BHi, Allocator>::~chunked_queue() {
Block* b = head_.block;
while (b) {
Block* next = b->next();
Destroy(block_begin(b), block_end(b));
Block::Delete(b, &alloc_and_size_.allocator());
b = next;
}
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
void chunked_queue<T, BLo, BHi, Allocator>::resize(size_t new_size) {
while (new_size > size()) {
ptrdiff_t to_add = new_size - size();
if (tail_.ptr == tail_.limit) {
AddTailBlock();
}
T* start = tail_.ptr;
T* limit = (std::min)(tail_.limit, start + to_add);
Construct(start, limit);
tail_.ptr = limit;
alloc_and_size_.size += limit - start;
}
if (size() == new_size) {
return;
}
ABSL_ASSERT(new_size < size());
auto new_end = begin();
new_end.IncrBy(new_size);
ABSL_ASSERT(new_end != end());
EraseAllFrom(new_end);
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
inline void chunked_queue<T, BLo, BHi, Allocator>::AddTailBlock() {
ABSL_ASSERT(tail_.ptr == tail_.limit);
auto* b = Block::New(NewBlockSize(), &alloc_and_size_.allocator());
if (!head_.block) {
ABSL_ASSERT(!tail_.block);
head_ = iterator(b);
} else {
ABSL_ASSERT(tail_.block);
tail_.block->set_next(b);
}
tail_ = iterator(b);
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
inline T* chunked_queue<T, BLo, BHi, Allocator>::AllocateBack() {
if (tail_.ptr == tail_.limit) {
AddTailBlock();
}
++alloc_and_size_.size;
return tail_.ptr++;
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
inline void chunked_queue<T, BLo, BHi, Allocator>::EraseAllFrom(iterator i) {
if (!i.block) {
return;
}
ABSL_ASSERT(i.ptr);
ABSL_ASSERT(i.limit);
alloc_and_size_.size -= Destroy(i.ptr, block_end(i.block));
Block* b = i.block->next();
while (b) {
Block* next = b->next();
alloc_and_size_.size -= Destroy(b->start(), block_end(b));
Block::Delete(b, &alloc_and_size_.allocator());
b = next;
}
tail_ = i;
tail_.block->set_next(nullptr);
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
inline void chunked_queue<T, BLo, BHi, Allocator>::DestroyAndDeallocateAll() {
Block* b = head_.block;
while (b) {
Block* next = b->next();
Destroy(block_begin(b), block_end(b));
Block::Delete(b, &alloc_and_size_.allocator());
b = next;
}
head_ = iterator();
tail_ = iterator();
alloc_and_size_.size = 0;
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
inline void chunked_queue<T, BLo, BHi, Allocator>::pop_front() {
ABSL_HARDENING_ASSERT(!empty());
ABSL_ASSERT(head_.block);
AllocatorTraits::destroy(alloc_and_size_.allocator(), head_.ptr);
++head_.ptr;
--alloc_and_size_.size;
if (empty()) {
ABSL_ASSERT(head_.block == tail_.block);
head_.ptr = tail_.ptr = head_.block->start();
return;
}
if (head_.ptr == head_.limit) {
Block* n = head_.block->next();
Block::Delete(head_.block, &alloc_and_size_.allocator());
head_ = iterator(n);
}
}
template <typename T, size_t BLo, size_t BHi, typename Allocator>
void chunked_queue<T, BLo, BHi, Allocator>::clear() {
Block* b = head_.block;
if (!b) {
ABSL_ASSERT(empty());
return;
}
while (b) {
Block* next = b->next();
Destroy(block_begin(b), block_end(b));
if (head_.block != b) {
Block::Delete(b, &alloc_and_size_.allocator());
}
b = next;
}
b = head_.block;
b->set_next(nullptr);
head_ = tail_ = iterator(b);
alloc_and_size_.size = 0;
}
ABSL_NAMESPACE_END
}
#endif