| // Copyright 2022 The Chromium 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 TOOLS_GN_IMMUTABLE_VECTOR_H_ |
| #define TOOLS_GN_IMMUTABLE_VECTOR_H_ |
| |
| #include <cstdlib> |
| #include <type_traits> |
| #include <utility> |
| |
| #include "util/aligned_alloc.h" |
| |
| // An ImmutableVector<T> represents a fixed-size vector of constant items of |
| // type T. The in-memory representation is more efficient, only using one |
| // pointer to a single heap-allocated memory block that also contains the size. |
| // |
| // An ImmutableVectorView<T> acts as a copyable and movable reference to another |
| // ImmutableVector<T> instance. They both point to the same memory in the heap, |
| // but the view is not owning and is invalidated when the instance it points to |
| // is destroyed. |
| // |
| // Apart from that, they can be used with the same methods as a const |
| // std::vector<T>. |
| // |
| template <typename T> |
| class ImmutableVector; |
| |
| template <typename T> |
| class ImmutableVectorView { |
| public: |
| // Default constructor |
| ImmutableVectorView() = default; |
| |
| // Constructor from an ImmutableVector. |
| ImmutableVectorView(const ImmutableVector<T>& vector) |
| : header_(vector.header_) {} |
| |
| // Usual methods to access items. |
| const T* data() const { return begin(); } |
| size_t size() const { return header_ ? header_->size : 0u; } |
| bool empty() const { return size() == 0; } |
| const T& operator[](size_t offset) const { return begin()[offset]; } |
| |
| const T* begin() const { return header_ ? &header_->item0 : nullptr; } |
| const T* end() const { |
| return header_ ? &header_->item0 + header_->size : nullptr; |
| } |
| |
| const T& front() const { return begin()[0]; } |
| const T& back() const { return end()[-1]; } |
| |
| // For use with standard algorithms and templates. |
| using element_type = T; |
| using value_type = std::remove_cv_t<T>; |
| using size_type = std::size_t; |
| using difference_type = std::ptrdiff_t; |
| using pointer = T*; |
| using const_pointer = const T*; |
| using reference = T&; |
| using const_reference = const T&; |
| using iterator = const T*; |
| using const_iterator = const T*; |
| |
| const_iterator find(const T& item) const { |
| auto it = begin(); |
| auto it_end = end(); |
| for (; it != it_end; ++it) { |
| if (*it == item) |
| break; |
| } |
| return it; |
| } |
| |
| bool contains(const T& item) const { |
| for (const auto& cur_item : *this) |
| if (cur_item == item) |
| return true; |
| |
| return false; |
| } |
| |
| protected: |
| struct Header { |
| size_t size; |
| T item0; |
| }; |
| |
| ImmutableVectorView(Header* header) : header_(header) {} |
| |
| Header* header_ = nullptr; |
| }; |
| |
| template <typename T> |
| class ImmutableVector : public ImmutableVectorView<T> { |
| public: |
| // Default constructor. |
| ImmutableVector() = default; |
| |
| // Range constructors. |
| ImmutableVector(const T* begin, size_t size) |
| : ImmutableVectorView<T>(AllocateAndCopyFrom(begin, size)) {} |
| |
| ImmutableVector(const T* begin, const T* end) |
| : ImmutableVector(begin, end - begin) {} |
| |
| // In-place constructor |
| // |producer| must be a callable that generates a T instance that will |
| // be used to construct items in place in the allocated vector. |
| template <typename P, |
| typename = std::void_t< |
| decltype(static_cast<const T&>(std::declval<P>()()))>> |
| ImmutableVector(P producer, size_t size) { |
| if (size) { |
| this->header_ = AllocateFor(size); |
| auto* dst = &(this->header_->item0); |
| auto* dst_limit = dst + size; |
| for (; dst != dst_limit; ++dst) |
| new (dst) T(producer()); |
| } |
| } |
| |
| // Container constructor |
| // |
| // This uses std::void_t<> to select container types whose values |
| // are convertible to |const T&|, and which have |begin()| and |size()| |
| // methods. |
| // |
| // This constructor copies items from the constructor into the |
| // ImmutableVector. |
| template <typename C, |
| typename = std::void_t< |
| decltype(static_cast<const T>(*std::declval<C>().begin())), |
| decltype(std::declval<C>().size())>> |
| ImmutableVector(const C& container) { |
| size_t size = container.size(); |
| if (size) { |
| this->header_ = AllocateFor(size); |
| auto src = container.begin(); |
| auto* dst = &(this->header_->item0); |
| auto* dst_limit = dst + size; |
| for (; dst != dst_limit; ++dst, ++src) { |
| new (dst) T(*src); |
| } |
| } |
| } |
| |
| // Another container constructor where the items can be moved into |
| // the resulting ImmutableVector. |
| template <typename C, |
| typename = std::void_t< |
| decltype(static_cast<const T>(*std::declval<C>().begin())), |
| decltype(std::declval<C>().size())>> |
| ImmutableVector(C&& container) { |
| size_t size = container.size(); |
| if (size) { |
| this->header_ = AllocateFor(size); |
| auto src = container.begin(); |
| auto* dst = &(this->header_->item0); |
| auto* dst_limit = dst + size; |
| for (; dst != dst_limit; ++dst, ++src) |
| new (dst) T(std::move(*src)); |
| } |
| } |
| |
| // Initializer-list container. |
| ImmutableVector(std::initializer_list<T> list) |
| : ImmutableVector(list.begin(), list.size()) {} |
| |
| // Copy operations |
| ImmutableVector(const ImmutableVector& other) |
| : ImmutableVectorView<T>( |
| AllocateAndCopyFrom(other.begin(), other.size())) {} |
| |
| ImmutableVector& operator=(const ImmutableVector& other) { |
| if (this != &other) { |
| this->~ImmutableVector(); |
| new (this) ImmutableVector(other); |
| } |
| return *this; |
| } |
| |
| // Move operations |
| ImmutableVector(ImmutableVector&& other) noexcept { |
| this->header_ = other.header_; |
| other.header_ = nullptr; |
| } |
| |
| ImmutableVector& operator=(ImmutableVector&& other) noexcept { |
| if (this != &other) { |
| this->~ImmutableVector(); |
| new (this) ImmutableVector(std::move(other)); |
| } |
| return *this; |
| } |
| |
| // Destructor |
| ~ImmutableVector() { |
| if (this->header_) { |
| auto* cur = &(this->header_->item0); |
| auto* limit = cur + this->size(); |
| while (cur != limit) { |
| (*cur).~T(); |
| cur++; |
| } |
| Allocator::Free(this->header_); |
| } |
| } |
| |
| protected: |
| friend class ImmutableVectorView<T>; |
| |
| using Header = typename ImmutableVectorView<T>::Header; |
| |
| // Don't use std::max() here to avoid including <algorithm> which is massive. |
| static constexpr size_t kAlignment = alignof(T) > alignof(Header) |
| ? alignof(T) |
| : alignof(Header); |
| |
| using Allocator = AlignedAlloc<kAlignment>; |
| |
| static Header* AllocateFor(size_t count) { |
| if (!count) |
| return nullptr; |
| |
| auto* header = reinterpret_cast<Header*>( |
| Allocator::Alloc(sizeof(Header) + (count - 1u) * sizeof(T))); |
| header->size = count; |
| return header; |
| } |
| |
| static Header* AllocateAndCopyFrom(const T* begin, size_t size) { |
| Header* header = AllocateFor(size); |
| if (size) { |
| T* dst = &(header->item0); |
| T* limit = dst + size; |
| for (; dst != limit; ++dst, ++begin) |
| new (dst) T(*begin); |
| } |
| return header; |
| } |
| }; |
| |
| #endif // TOOLS_GN_IMMUTABLE_VECTOR_H_ |