blob: a81cbbdb509ea50e11b6e6a6760985771000eb90 [file] [log] [blame]
// Copyright 2021 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 FBL_STATIC_VECTOR_H_
#define FBL_STATIC_VECTOR_H_
#include <zircon/assert.h>
#include <array>
#include <iterator>
#include <type_traits>
#include <utility>
namespace fbl {
namespace internal {
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0843r4.html#STORAGE
// "If the Capacity is zero the container has zero size."
template <typename T>
class static_vector_storage_empty {
public:
constexpr T* data() noexcept { return nullptr; }
constexpr const T* data() const noexcept { return nullptr; }
constexpr size_t size() const noexcept { return 0; }
constexpr void set_size(size_t new_size) noexcept {}
template <class... Args>
constexpr void construct_at(size_t k, Args&&... args) {}
constexpr void destroy_at(size_t k) {}
};
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0843r4.html#CONSTEXPR
// "If is_trivially_copyable_v<T> && is_default_constructible_v<T> is true, static_vectors
// can be seamlessly used from constexpr code ... This changes the algorithmic complexity of
// static_vector constructors for trivial-types from 'Linear in N' to 'Constant in Capacity'."
//
// Note that TrivallyCopyable implies that T must have a trivial destructor:
// https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable
template <typename T, size_t N>
class static_vector_storage_trivial {
private:
size_t size_{0};
std::array<T, N> raw_data_{};
public:
constexpr T* data() noexcept { return &raw_data_[0]; }
constexpr const T* data() const noexcept { return &raw_data_[0]; }
constexpr size_t size() const noexcept { return size_; }
constexpr void set_size(size_t new_size) noexcept { size_ = new_size; }
// Since this class is used only when is_trivially_copyable_v<T>, it doesn't matter
// whether or not the old element at k has been destructed because T's destructor
// is a no-op. This should compile to a memmove.
template <class... Args>
constexpr void construct_at(size_t k, Args&&... args) {
raw_data_[k] = T(std::forward<Args>(args)...);
}
// Since T must be trivially destructible, this is a no-op.
constexpr void destroy_at(size_t k) {}
};
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0843r4.html#STORAGE
// "The container models ContiguousContainer. The elements of the static_vector are
// contiguously stored and properly aligned within the static_vector object itself.
// The exact location of the contiguous elements within the static_vector is not specified."
template <typename T, size_t N>
class static_vector_storage_nontrivial {
private:
size_t size_{0};
typename std::aligned_storage<sizeof(T), alignof(T)>::type raw_data_[N];
public:
T* data() noexcept { return reinterpret_cast<T*>(&raw_data_[0]); }
const T* data() const noexcept { return reinterpret_cast<const T*>(&raw_data_[0]); }
size_t size() const noexcept { return size_; }
void set_size(size_t new_size) noexcept { size_ = new_size; }
~static_vector_storage_nontrivial() {
for (auto p = data(); p < data() + size_; p++) {
p->~T();
}
}
// Requires that raw_data_[k] is in an unconstructed state.
template <class... Args>
void construct_at(size_t k, Args&&... args) {
new (&raw_data_[k]) T(std::forward<Args>(args)...);
}
// Requires that raw_data_[k] is in a constructed state.
void destroy_at(size_t k) { data()[k].~T(); }
};
template <typename T, size_t N>
struct static_vector_storage {
using type = std::conditional_t<
N == 0, internal::static_vector_storage_empty<T>,
std::conditional_t<std::is_trivially_copyable_v<T> && std::is_default_constructible_v<T>,
internal::static_vector_storage_trivial<T, N>,
internal::static_vector_storage_nontrivial<T, N>>>;
};
template <typename T, size_t N>
using static_vector_storage_t = typename static_vector_storage<T, N>::type;
} // namespace internal
// This class implements a resizable vector with fixed capacity known at compile time.
// This is a partial implementation of the following proposal:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0843r4.html
//
// For now we have elided a few unneeded methods:
// - swap() method and std::swap() specialization
// - insert(), emplace(), and erase()
// - emplace_back()
// - (in)equality operators
//
template <typename T, size_t N>
class static_vector : private internal::static_vector_storage_t<T, N> {
private:
using base_type = typename internal::static_vector_storage_t<T, N>;
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = value_type&;
using const_reference = const value_type&;
using size_type = size_t;
using difference_type = ptrdiff_t;
using iterator = T*;
using const_iterator = const T*;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
public:
//
// Construction
//
constexpr static_vector() noexcept = default;
constexpr static_vector(const static_vector& rhs) { *this = rhs; }
constexpr static_vector(static_vector&& rhs) { *this = std::move(rhs); }
// Construct a vector of the given size with n default-constructed elements.
// Requires n <= N.
constexpr explicit static_vector(size_type n) { resize(n); }
// Construct a vector of the given size with n copies of value.
// Requires n <= N.
constexpr static_vector(size_type n, const value_type& value) { resize(n, value); }
// Construct a vector as a copy of the range [first, last]
// Requires std::distance(first, last) <= N.
template <class InputIterator>
constexpr static_vector(InputIterator first, InputIterator last) {
assign(first, last);
}
// Construct a vector as a copy of the initializer list.
// Requires init.size() <= N.
constexpr static_vector(std::initializer_list<value_type> init) { assign(init); }
~static_vector() = default;
//
// Assignment
//
constexpr static_vector& operator=(const static_vector& rhs) {
clear();
set_size(rhs.size());
for (size_type k = 0; k < size(); k++) {
construct_at(k, rhs[k]);
}
return *this;
}
constexpr static_vector& operator=(static_vector&& rhs) {
clear();
set_size(rhs.size());
for (size_type k = 0; k < size(); k++) {
construct_at(k, std::move(rhs[k]));
}
return *this;
}
template <class InputIterator>
constexpr void assign(InputIterator first, InputIterator last) {
clear();
size_type k = 0;
for (; first != last; k++, first++) {
ZX_DEBUG_ASSERT(k < N);
construct_at(k, *first);
}
set_size(k);
}
constexpr void assign(size_type n, const value_type& value) {
clear();
resize(n, value);
}
constexpr void assign(std::initializer_list<value_type> init) {
clear();
ZX_DEBUG_ASSERT(init.size() <= N);
size_type k = 0;
for (const auto& v : init) {
construct_at(k, v);
k++;
}
set_size(k);
}
//
// Iterators
//
// clang-format off
constexpr iterator begin() noexcept { return data(); }
constexpr const_iterator begin() const noexcept { return data(); }
constexpr iterator end() noexcept { return data() + size(); }
constexpr const_iterator end() const noexcept { return data() + size(); }
constexpr reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
constexpr reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
constexpr const_iterator cbegin() const noexcept { return begin(); }
constexpr const_iterator cend() const noexcept { return end(); }
constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); }
constexpr const_reverse_iterator crend() const noexcept { return rend(); }
// clang-format on
//
// Size and capacity
//
// Returns true if the vector is currently empty;
constexpr bool empty() const noexcept { return size() == 0; }
// Returns the current size of the vector.
using base_type::size;
// Returns the maximum possible size of the vector.
static constexpr size_type max_size() noexcept { return N; }
static constexpr size_type capacity() noexcept { return N; }
// Resize the vector to the give size. If the vector shrinks, the erased items
// are destructed. If the vector grows, the new items are default constructed.
//
// resize() does not compile with non-default-constructible types, because
// growing the vector requires the ability to default-construct elements.
constexpr void resize(size_type new_size) {
ZX_DEBUG_ASSERT(new_size <= N);
if (new_size < size()) {
destroy_range(new_size, size());
} else {
construct_range(size(), new_size);
}
set_size(new_size);
}
// Resize the vector to the give size. If the vector shrinks, the erased items
// are destructed. If the vector grows, the new items are assigned a copy of the
// given value.
constexpr void resize(size_type new_size, const value_type& value) {
ZX_DEBUG_ASSERT(new_size <= N);
if (new_size < size()) {
destroy_range(new_size, size());
} else {
construct_range(size(), new_size, value);
}
set_size(new_size);
}
//
// Element access
//
// clang-format off
constexpr reference operator[](size_type n) { return data()[n]; }
constexpr const_reference operator[](size_type n) const { return data()[n]; }
constexpr reference front() { return data()[0]; }
constexpr const_reference front() const { return data()[0]; }
constexpr reference back() { return data()[size() - 1]; }
constexpr const_reference back() const { return data()[size() - 1]; }
// clang-format on
using base_type::data;
//
// Modifiers
//
constexpr void push_back(const value_type& value) {
ZX_DEBUG_ASSERT(size() < N);
construct_at(size(), value);
set_size(size() + 1);
}
constexpr void push_back(value_type&& value) {
ZX_DEBUG_ASSERT(size() < N);
construct_at(size(), std::move(value));
set_size(size() + 1);
}
constexpr void pop_back() {
ZX_DEBUG_ASSERT(size() > 0);
// This is an inlined version of resize(size() - 1). We don't want to call
// resize() because it does not compile for non-default-constructible
// objects.
destroy_at(size() - 1);
set_size(size() - 1);
}
constexpr void clear() noexcept {
// This is an inlined version of resize(0). We don't want to call resize(),
// because it does not compile for non-default-constructible objects.
destroy_range(0, size());
set_size(0);
}
private:
using base_type::construct_at;
using base_type::destroy_at;
using base_type::set_size;
template <class... Args>
constexpr void construct_range(size_type first, size_type last, Args&&... args) {
ZX_DEBUG_ASSERT(last <= N);
for (; first < last; first++) {
construct_at(first, std::forward<Args>(args)...);
}
}
constexpr void destroy_range(size_type first, size_type last) {
ZX_DEBUG_ASSERT(last <= N);
for (; first < last; first++) {
destroy_at(first);
}
}
};
} // namespace fbl
#endif // FBL_STATIC_VECTOR_H_