blob: 32c741b12562cb962234c8cb7996c314481f9cc7 [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <limits>
#include <new>
#include <string_view>
#include <type_traits>
#include <utility>
#include "pw_assert/assert.h"
#include "pw_polyfill/language_feature_macros.h"
namespace pw {
namespace vector_impl {
template <typename I>
using IsIterator = std::negation<
std::is_same<typename std::iterator_traits<I>::value_type, void>>;
// Used as max_size in the generic-size Vector<T> interface.
PW_INLINE_VARIABLE constexpr size_t kGeneric = size_t(-1);
// The DestructorHelper is used to make Vector<T> trivially destructible if T
// is. This could be replaced with a C++20 constraint.
template <typename VectorClass, bool kIsTriviallyDestructible>
class DestructorHelper;
template <typename VectorClass>
class DestructorHelper<VectorClass, true> {
public:
~DestructorHelper() = default;
};
template <typename VectorClass>
class DestructorHelper<VectorClass, false> {
public:
~DestructorHelper() { static_cast<VectorClass*>(this)->clear(); }
};
} // namespace vector_impl
// The Vector class is similar to std::vector, except it is backed by a
// fixed-size buffer. Vectors must be declared with an explicit maximum size
// (e.g. Vector<int, 10>) but vectors can be used and referred to without the
// max size template parameter (e.g. Vector<int>).
//
// To allow referring to a pw::Vector without an explicit maximum size, all
// Vector classes inherit from Vector<T, vector_impl::kGeneric>, which stores
// the maximum size in a variable. This allows Vectors to be used without having
// to know their maximum size at compile time. It also keeps code size small
// since function implementations are shared for all maximum sizes.
template <typename T, size_t kMaxSize = vector_impl::kGeneric>
class Vector : public Vector<T, vector_impl::kGeneric> {
public:
using typename Vector<T, vector_impl::kGeneric>::value_type;
using typename Vector<T, vector_impl::kGeneric>::size_type;
using typename Vector<T, vector_impl::kGeneric>::difference_type;
using typename Vector<T, vector_impl::kGeneric>::reference;
using typename Vector<T, vector_impl::kGeneric>::const_reference;
using typename Vector<T, vector_impl::kGeneric>::pointer;
using typename Vector<T, vector_impl::kGeneric>::const_pointer;
using typename Vector<T, vector_impl::kGeneric>::iterator;
using typename Vector<T, vector_impl::kGeneric>::const_iterator;
using typename Vector<T, vector_impl::kGeneric>::reverse_iterator;
using typename Vector<T, vector_impl::kGeneric>::const_reverse_iterator;
// Construct
Vector() noexcept : Vector<T, vector_impl::kGeneric>(kMaxSize) {}
Vector(size_type count, const T& value)
: Vector<T, vector_impl::kGeneric>(kMaxSize, count, value) {}
explicit Vector(size_type count)
: Vector<T, vector_impl::kGeneric>(kMaxSize, count, T()) {}
template <
typename Iterator,
typename...,
typename = std::enable_if_t<vector_impl::IsIterator<Iterator>::value>>
Vector(Iterator first, Iterator last)
: Vector<T, vector_impl::kGeneric>(kMaxSize, first, last) {}
Vector(const Vector& other)
: Vector<T, vector_impl::kGeneric>(kMaxSize, other) {}
template <size_t kOtherMaxSize>
Vector(const Vector<T, kOtherMaxSize>& other)
: Vector<T, vector_impl::kGeneric>(kMaxSize, other) {}
Vector(Vector&& other) noexcept
: Vector<T, vector_impl::kGeneric>(kMaxSize, std::move(other)) {}
template <size_t kOtherMaxSize>
Vector(Vector<T, kOtherMaxSize>&& other) noexcept
: Vector<T, vector_impl::kGeneric>(kMaxSize, std::move(other)) {}
Vector(std::initializer_list<T> list)
: Vector<T, vector_impl::kGeneric>(kMaxSize, list) {}
// Construct from std::string_view when T is char.
template <typename U = T,
typename = std::enable_if_t<std::is_same_v<U, char>>>
Vector(std::string_view source) : Vector(source.begin(), source.end()) {}
// Construct from const char* when T is char.
template <typename U = T,
typename = std::enable_if_t<std::is_same_v<U, char>>>
Vector(const char* source) : Vector(std::string_view(source)) {}
Vector& operator=(const Vector& other) {
Vector<T>::assign(other.begin(), other.end());
return *this;
}
template <size_t kOtherMaxSize>
Vector& operator=(const Vector<T, kOtherMaxSize>& other) noexcept {
Vector<T>::assign(other.begin(), other.end());
return *this;
}
Vector& operator=(Vector&& other) noexcept {
Vector<T>::operator=(std::move(other));
return *this;
}
template <size_t kOtherMaxSize>
Vector& operator=(Vector<T, kOtherMaxSize>&& other) noexcept {
Vector<T>::operator=(std::move(other));
return *this;
}
Vector& operator=(std::initializer_list<T> list) {
this->assign(list.begin(), list.end());
return *this;
}
// All other vector methods are implemented on the Vector<T> base class.
private:
friend class Vector<T, vector_impl::kGeneric>;
static_assert(kMaxSize <= std::numeric_limits<size_type>::max());
// Provides access to the underlying array as an array of T.
#ifdef __cpp_lib_launder
pointer array() { return std::launder(reinterpret_cast<T*>(&array_)); }
const_pointer array() const {
return std::launder(reinterpret_cast<const T*>(&array_));
}
#else
pointer array() { return reinterpret_cast<T*>(&array_); }
const_pointer array() const { return reinterpret_cast<const T*>(&array_); }
#endif // __cpp_lib_launder
// Vector entries are stored as uninitialized memory blocks aligned correctly
// for the type. Elements are initialized on demand with placement new.
//
// This uses std::array instead of a C array to support zero-length Vectors.
// Zero-length C arrays are non-standard, but std::array<T, 0> is valid.
// The alignas specifier is required ensure that a zero-length array is
// aligned the same as an array with elements.
alignas(T) std::array<std::aligned_storage_t<sizeof(T), alignof(T)>,
kMaxSize> array_;
};
// Defines the generic-sized Vector<T> specialization, which serves as the base
// class for Vector<T> of any maximum size. Except for constructors, all Vector
// methods are implemented on this class.
template <typename T>
class Vector<T, vector_impl::kGeneric>
: public vector_impl::DestructorHelper<
Vector<T, vector_impl::kGeneric>,
std::is_trivially_destructible<T>::value> {
public:
using value_type = T;
// Use unsigned short instead of size_t. Since Vectors are statically
// allocated, 65535 entries is a reasonable upper limit. This reduces Vector's
// overhead by packing the size and capacity into 32 bits.
using size_type = unsigned short;
using difference_type = ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = T*;
using const_pointer = const 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>;
// A vector without an explicit maximum size (Vector<T>) cannot be constructed
// directly. Instead, construct a Vector<T, kMaxSize>. Vectors of any max size
// can be used through a Vector<T> pointer or reference.
// Assign
Vector& operator=(const Vector& other) {
assign(other.begin(), other.end());
return *this;
}
Vector& operator=(Vector&& other) noexcept {
clear();
MoveFrom(other);
return *this;
}
Vector& operator=(std::initializer_list<T> list) {
assign(list);
return *this;
}
void assign(size_type count, const T& value) {
clear();
Append(count, value);
}
template <
typename Iterator,
typename...,
typename = std::enable_if_t<vector_impl::IsIterator<Iterator>::value>>
void assign(Iterator first, Iterator last) {
clear();
CopyFrom(first, last);
}
void assign(std::initializer_list<T> list) {
assign(list.begin(), list.end());
}
// Access
reference at(size_type index) {
PW_ASSERT(index < size());
return data()[index];
}
const_reference at(size_type index) const {
PW_ASSERT(index < size());
return data()[index];
}
reference operator[](size_type index) {
PW_DASSERT(index < size());
return data()[index];
}
const_reference operator[](size_type index) const {
PW_DASSERT(index < size());
return data()[index];
}
reference front() { return data()[0]; }
const_reference front() const { return data()[0]; }
reference back() { return data()[size() - 1]; }
const_reference back() const { return data()[size() - 1]; }
// The underlying data is not part of the generic-length vector class. It is
// provided in the derived class from which this instance was constructed. To
// access the data, down-cast this to a Vector with a known max size, and
// return a pointer to the start of the array, which is the same for all
// vectors with explicit max size.
T* data() noexcept { return static_cast<Vector<T, 0>*>(this)->array(); }
const T* data() const noexcept {
return static_cast<const Vector<T, 0>*>(this)->array();
}
// Returns a std::string_view of the contents of this Vector when T is char.
// The std::string_view is invalidated if the vector contents change.
template <typename U = T,
typename = std::enable_if_t<std::is_same_v<U, char>>>
constexpr std::string_view view() const {
return std::string_view(data(), size());
}
// Allow implicit conversions to std::string_view when T is char so Vectors
// can be passed into functions that take a std::string_view.
template <typename U = T,
typename = std::enable_if_t<std::is_same_v<U, char>>>
constexpr operator std::string_view() const {
return view();
}
// Iterate
iterator begin() noexcept { return &data()[0]; }
const_iterator begin() const noexcept { return &data()[0]; }
const_iterator cbegin() const noexcept { return &data()[0]; }
iterator end() noexcept { return &data()[size()]; }
const_iterator end() const noexcept { return &data()[size()]; }
const_iterator cend() const noexcept { return &data()[size()]; }
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const { return reverse_iterator(end()); }
const_reverse_iterator crbegin() const noexcept {
return reverse_iterator(cend());
}
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rend() const { return reverse_iterator(begin()); }
const_reverse_iterator crend() const noexcept {
return reverse_iterator(cbegin());
}
// Size
[[nodiscard]] bool empty() const noexcept { return size() == 0u; }
// True if there is no free space in the vector. Operations that add elements
// (push_back, insert, etc.) will fail if full() is true.
[[nodiscard]] bool full() const noexcept { return size() == max_size(); }
// Returns the number of elements in the Vector. Uses size_t instead of
// size_type for consistency with other containers.
size_t size() const noexcept { return size_; }
// Returns the maximum number of elements in this Vector.
size_t max_size() const noexcept { return max_size_; }
size_t capacity() const noexcept { return max_size(); }
// Modify
void clear() noexcept;
// TODO(hepler): insert, emplace, and erase are not yet implemented.
// Currently, items can only be added to or removed from the end.
iterator insert(const_iterator index, const T& value);
iterator insert(const_iterator index, T&& value);
iterator insert(const_iterator index, size_type count, const T& value);
template <typename Iterator>
iterator insert(const_iterator index, Iterator first, Iterator last);
iterator insert(const_iterator index, std::initializer_list<T> list);
template <typename... Args>
iterator emplace(const_iterator index, Args&&... args);
iterator erase(const_iterator index);
iterator erase(const_iterator first, const_iterator last);
void push_back(const T& value) { emplace_back(value); }
void push_back(T&& value) { emplace_back(std::move(value)); }
template <typename... Args>
void emplace_back(Args&&... args);
void pop_back();
void resize(size_type new_size) { resize(new_size, T()); }
void resize(size_type new_size, const T& value);
protected:
// Vectors without an explicit size cannot be constructed directly. Instead,
// the maximum size must be provided.
explicit constexpr Vector(size_type max_size) noexcept
: max_size_(max_size) {}
Vector(size_type max_size, size_type count, const T& value)
: Vector(max_size) {
Append(count, value);
}
Vector(size_type max_size, size_type count) : Vector(max_size, count, T()) {}
template <
typename Iterator,
typename...,
typename = std::enable_if_t<vector_impl::IsIterator<Iterator>::value>>
Vector(size_type max_size, Iterator first, Iterator last) : Vector(max_size) {
CopyFrom(first, last);
}
Vector(size_type max_size, const Vector& other) : Vector(max_size) {
CopyFrom(other.begin(), other.end());
}
Vector(size_type max_size, Vector&& other) noexcept : Vector(max_size) {
MoveFrom(other);
}
Vector(size_type max_size, std::initializer_list<T> list) : Vector(max_size) {
CopyFrom(list.begin(), list.end());
}
private:
template <typename Iterator>
void CopyFrom(Iterator first, Iterator last);
void MoveFrom(Vector& other) noexcept;
void Append(size_type count, const T& value);
const size_type max_size_;
size_type size_ = 0;
};
// Compare
template <typename T, size_t kLhsSize, size_t kRhsSize>
bool operator==(const Vector<T, kLhsSize>& lhs,
const Vector<T, kRhsSize>& rhs) {
return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename T, size_t kLhsSize, size_t kRhsSize>
bool operator!=(const Vector<T, kLhsSize>& lhs,
const Vector<T, kRhsSize>& rhs) {
return !(lhs == rhs);
}
template <typename T, size_t kLhsSize, size_t kRhsSize>
bool operator<(const Vector<T, kLhsSize>& lhs, const Vector<T, kRhsSize>& rhs) {
return std::lexicographical_compare(
lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename T, size_t kLhsSize, size_t kRhsSize>
bool operator<=(const Vector<T, kLhsSize>& lhs,
const Vector<T, kRhsSize>& rhs) {
return !(rhs < lhs);
}
template <typename T, size_t kLhsSize, size_t kRhsSize>
bool operator>(const Vector<T, kLhsSize>& lhs, const Vector<T, kRhsSize>& rhs) {
return rhs < lhs;
}
template <typename T, size_t kLhsSize, size_t kRhsSize>
bool operator>=(const Vector<T, kLhsSize>& lhs,
const Vector<T, kRhsSize>& rhs) {
return !(lhs < rhs);
}
// Function implementations
template <typename T>
void Vector<T, vector_impl::kGeneric>::clear() noexcept {
for (auto& item : *this) {
item.~T();
}
size_ = 0;
}
template <typename T>
template <typename... Args>
void Vector<T, vector_impl::kGeneric>::emplace_back(Args&&... args) {
if (!full()) {
new (&data()[size_]) T(std::forward<Args>(args)...);
size_ += 1;
}
}
template <typename T>
void Vector<T, vector_impl::kGeneric>::pop_back() {
if (!empty()) {
back().~T();
size_ -= 1;
}
}
template <typename T>
void Vector<T, vector_impl::kGeneric>::resize(size_type new_size,
const T& value) {
if (size() < new_size) {
Append(std::min(max_size(), size_t(new_size)) - size(), value);
} else {
while (size() > new_size) {
pop_back();
}
}
}
template <typename T>
template <typename Iterator>
void Vector<T, vector_impl::kGeneric>::CopyFrom(Iterator first, Iterator last) {
while (first != last) {
push_back(*first++);
}
}
template <typename T>
void Vector<T, vector_impl::kGeneric>::MoveFrom(Vector& other) noexcept {
for (auto&& item : other) {
emplace_back(std::move(item));
}
other.clear();
}
template <typename T>
void Vector<T, vector_impl::kGeneric>::Append(size_type count, const T& value) {
for (size_t i = 0; i < count; ++i) {
push_back(value);
}
}
} // namespace pw