blob: 665675737d440dc902dd391b0668bb6b780562b0 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_PREALLOCATED_VECTOR_H_
#define SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_PREALLOCATED_VECTOR_H_
#include <lib/stdcompat/span.h>
#include <new>
#include <optional>
#include <string_view>
#include <type_traits>
namespace elfldltl {
// elfldltl::PreallocatedVector<T> wraps a previously allocated but
// uninitialized span of T with a container interface that looks like a
// std::vector but provides the container.h API for the allocating methods.
// For an allocation failure, the Diagnostics object's ResourceLimit<N> method
// will be called with the error string (the std::string_view parameter). The
// methods that usually return void (push_back, emplace_back) instead return
// bool, with false indicating allocation failure. The methods that usually
// return an iterator (emplace, insert) instead return std::optional<iterator>,
// with std::nullopt indicating allocation failure.
//
// This can be default-constructed (with zero capacity) and then move-assigned
// from another object constructed with storage. It's usually constructed via
// the deduction guide with any cpp20::span argument.
template <typename T, size_t N = cpp20::dynamic_extent>
class PreallocatedVector {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = typename cpp20::span<T>::iterator;
using const_iterator = typename cpp20::span<const T, N>::iterator;
using reverse_iterator = typename cpp20::span<T, N>::reverse_iterator;
using const_reverse_iterator = typename cpp20::span<const T, N>::reverse_iterator;
constexpr PreallocatedVector() = default;
constexpr PreallocatedVector(PreallocatedVector&&) noexcept = default;
template <size_t OtherN>
constexpr explicit PreallocatedVector(PreallocatedVector<T, OtherN>&& other) noexcept
: PreallocatedVector(other.storage_) {
static_assert(N == cpp20::dynamic_extent || OtherN == N);
}
constexpr explicit PreallocatedVector(cpp20::span<T, N> storage) : storage_(storage) {}
constexpr PreallocatedVector& operator=(PreallocatedVector&&) noexcept = default;
template <size_t OtherN>
constexpr PreallocatedVector& operator=(PreallocatedVector<T, OtherN>&& other) noexcept {
static_assert(N == cpp20::dynamic_extent || OtherN == N);
*this = PreallocatedVector(other.storage_);
return *this;
}
~PreallocatedVector() { clear(); }
// All the basic std::vector methods that don't need to allocate are here.
static constexpr size_t max_size() { return N; }
constexpr size_t capacity() const { return storage_.size(); }
constexpr cpp20::span<T> as_span() { return storage_.subspan(0, size_); }
constexpr cpp20::span<const T> as_span() const { return storage_.subspan(0, size_); }
constexpr pointer data() { return as_span().data(); }
constexpr const_pointer data() const { return as_span().data(); }
constexpr size_t size() const { return as_span().size(); }
constexpr bool empty() const { return as_span().empty(); }
constexpr iterator begin() { return as_span().begin(); }
constexpr iterator end() { return as_span().end(); }
constexpr const_iterator begin() const { return as_span().begin(); }
constexpr const_iterator end() const { return as_span().end(); }
constexpr const_iterator cbegin() const { return as_span().begin(); }
constexpr const_iterator cend() const { return as_span().end(); }
constexpr reverse_iterator rbegin() { return as_span().rbegin(); }
constexpr reverse_iterator rend() { return as_span().rend(); }
constexpr const_reverse_iterator rbegin() const { return crbegin(); }
constexpr const_reverse_iterator rend() const { return crend(); }
constexpr const_reverse_iterator crbegin() const { return as_span().rbegin(); }
constexpr const_reverse_iterator crend() const { return as_span().rend(); }
constexpr reference at(size_t pos) { return as_span()[pos]; }
constexpr const_reference at(size_t pos) const { return as_span()[pos]; }
constexpr reference operator[](size_t pos) { return at(pos); }
constexpr const_reference operator[](size_t pos) const { return at(pos); }
constexpr reference front() { return *begin(); }
constexpr const_reference front() const { return *begin(); }
constexpr reference back() { return *std::prev(end()); }
constexpr const_reference back() const { return *std::prev(end()); }
constexpr void pop_back() {
assert(size_ > 0);
back().~T();
--size_;
}
constexpr void clear() {
while (!empty()) {
pop_back();
}
}
constexpr iterator erase(iterator pos) { return erase(pos, pos); }
constexpr iterator erase(const_iterator pos) { return erase(pos, pos); }
constexpr iterator erase(iterator first, iterator last) {
iterator out = first;
for (iterator in = std::next(last); in != end(); ++in) {
*out++ = std::move(*in);
}
while (out != end()) {
pop_back();
}
return first;
}
constexpr iterator erase(const_iterator first, const_iterator last) {
return erase(begin() + (first - cbegin()), begin() + (last - cbegin()));
}
constexpr void resize(size_t new_size) {
assert(new_size <= size_);
while (new_size < size_) {
pop_back();
}
}
// The cases that require allocation are only supported via the methods
// that use the diagnostics.h API to report failures.
template <class Diagnostics>
constexpr bool resize(Diagnostics& diagnostics, std::string_view error, size_t new_size) {
if (new_size > capacity()) [[unlikely]] {
ResourceLimit(diagnostics, error, new_size);
return false;
}
if (new_size < size_) {
resize(new_size);
return true;
}
// Default-construct the new elements.
size_t old_size = size_;
size_ = new_size;
for (auto& elt : cpp20::span(data(), capacity()).subspan(old_size)) {
new (&elt) T();
}
return true;
}
template <class Diagnostics>
constexpr bool push_back(Diagnostics& diagnostics, std::string_view error, const T& elt) {
return emplace_back(diagnostics, error, elt);
}
template <class Diagnostics>
constexpr bool push_back(Diagnostics& diagnostics, std::string_view error, T&& elt) {
return emplace_back(diagnostics, error, std::move(elt));
}
template <class Diagnostics, typename... Args>
constexpr bool emplace_back(Diagnostics& diagnostics, std::string_view error, Args&&... args) {
if (size_ >= capacity()) [[unlikely]] {
ResourceLimit(diagnostics, error, capacity() + 1);
return false;
}
new (std::addressof(data()[size_++])) T{std::forward<Args>(args)...};
return true;
}
template <class Diagnostics, typename... Args>
constexpr std::optional<iterator> emplace(Diagnostics& diagnostics, std::string_view error,
const_iterator it, Args&&... args) {
if (size_ >= capacity()) [[unlikely]] {
ResourceLimit(diagnostics, error, capacity() + 1);
return std::nullopt;
}
// The new element will go where the existing elements are now.
iterator write = MoveElements(it, 1);
// Construct the new element.
new (std::addressof(*write)) T{std::forward<Args>(args)...};
// Return the iterator to where it was written.
return write;
}
template <class Diagnostics>
constexpr std::optional<iterator> insert(Diagnostics& diagnostics, std::string_view error,
const_iterator it, const T& value) {
return emplace(diagnostics, error, it, value);
}
template <class Diagnostics>
constexpr std::optional<iterator> insert(Diagnostics& diagnostics, std::string_view error,
const_iterator it, T&& value) {
return emplace(diagnostics, error, it, std::move(value));
}
template <class Diagnostics, typename InputIt>
constexpr std::optional<iterator> insert(Diagnostics& diagnostics, std::string_view error,
const_iterator it, InputIt first, InputIt last) {
const size_t count = std::distance(first, last);
if (capacity() - size_ < count) [[unlikely]] {
ResourceLimit(diagnostics, error, size_ + count);
return std::nullopt;
}
// The new elements will go where the existing elements are now.
iterator write = MoveElements(it, count);
iterator ret = write;
// Copy/move-construct the new elements.
while (first != last) {
new (std::addressof(*write++)) T{*first++};
}
// Return the iterator to the beginning where they were written.
return ret;
}
private:
template <class Diagnostics, typename... Args>
constexpr void ResourceLimit(Diagnostics& diag, Args... args) {
if constexpr (N != cpp20::dynamic_extent) {
diag.template ResourceLimit<N>(args...);
} else {
diag.ResourceLimit(capacity(), args...);
}
}
template <typename OtherT, size_t OtherN>
friend class PreallocatedVector;
// Return it+count after increasing size by count and moving all the
// elements at [it, it+count) up.
constexpr iterator MoveElements(const_iterator it, size_t count) {
iterator ret = begin() + (it - cbegin());
for (iterator write = (end() - 1 + count); write - count >= ret; write--) {
*write = std::move(*(write - count));
}
// Now advance end() to include the new slots.
size_ += count;
return ret;
}
size_t size_ = 0;
cpp20::span<T, N> storage_;
};
// Deduction guide.
template <typename T, size_t N>
PreallocatedVector(cpp20::span<T, N>) -> PreallocatedVector<T, N>;
} // namespace elfldltl
#endif // SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_PREALLOCATED_VECTOR_H_