1 // Copyright 2022 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef TOOLS_GN_IMMUTABLE_VECTOR_H_ 6 #define TOOLS_GN_IMMUTABLE_VECTOR_H_ 7 8 #include <cstdlib> 9 #include <type_traits> 10 #include <utility> 11 12 #include "util/aligned_alloc.h" 13 14 // An ImmutableVector<T> represents a fixed-size vector of constant items of 15 // type T. The in-memory representation is more efficient, only using one 16 // pointer to a single heap-allocated memory block that also contains the size. 17 // 18 // An ImmutableVectorView<T> acts as a copyable and movable reference to another 19 // ImmutableVector<T> instance. They both point to the same memory in the heap, 20 // but the view is not owning and is invalidated when the instance it points to 21 // is destroyed. 22 // 23 // Apart from that, they can be used with the same methods as a const 24 // std::vector<T>. 25 // 26 template <typename T> 27 class ImmutableVector; 28 29 template <typename T> 30 class ImmutableVectorView { 31 public: 32 // Default constructor 33 ImmutableVectorView() = default; 34 35 // Constructor from an ImmutableVector. ImmutableVectorView(const ImmutableVector<T> & vector)36 ImmutableVectorView(const ImmutableVector<T>& vector) 37 : header_(vector.header_) {} 38 39 // Usual methods to access items. data()40 const T* data() const { return begin(); } size()41 size_t size() const { return header_ ? header_->size : 0u; } empty()42 bool empty() const { return size() == 0; } 43 const T& operator[](size_t offset) const { return begin()[offset]; } 44 begin()45 const T* begin() const { return header_ ? &header_->item0 : nullptr; } end()46 const T* end() const { 47 return header_ ? &header_->item0 + header_->size : nullptr; 48 } 49 front()50 const T& front() const { return begin()[0]; } back()51 const T& back() const { return end()[-1]; } 52 53 // For use with standard algorithms and templates. 54 using element_type = T; 55 using value_type = std::remove_cv_t<T>; 56 using size_type = std::size_t; 57 using difference_type = std::ptrdiff_t; 58 using pointer = T*; 59 using const_pointer = const T*; 60 using reference = T&; 61 using const_reference = const T&; 62 using iterator = const T*; 63 using const_iterator = const T*; 64 find(const T & item)65 const_iterator find(const T& item) const { 66 auto it = begin(); 67 auto it_end = end(); 68 for (; it != it_end; ++it) { 69 if (*it == item) 70 break; 71 } 72 return it; 73 } 74 contains(const T & item)75 bool contains(const T& item) const { 76 for (const auto& cur_item : *this) 77 if (cur_item == item) 78 return true; 79 80 return false; 81 } 82 83 protected: 84 struct Header { 85 size_t size; 86 T item0; 87 }; 88 ImmutableVectorView(Header * header)89 ImmutableVectorView(Header* header) : header_(header) {} 90 91 Header* header_ = nullptr; 92 }; 93 94 template <typename T> 95 class ImmutableVector : public ImmutableVectorView<T> { 96 public: 97 // Default constructor. 98 ImmutableVector() = default; 99 100 // Range constructors. ImmutableVector(const T * begin,size_t size)101 ImmutableVector(const T* begin, size_t size) 102 : ImmutableVectorView<T>(AllocateAndCopyFrom(begin, size)) {} 103 ImmutableVector(const T * begin,const T * end)104 ImmutableVector(const T* begin, const T* end) 105 : ImmutableVector(begin, end - begin) {} 106 107 // In-place constructor 108 // |producer| must be a callable that generates a T instance that will 109 // be used to construct items in place in the allocated vector. 110 template <typename P, 111 typename = std::void_t< 112 decltype(static_cast<const T&>(std::declval<P>()()))>> ImmutableVector(P producer,size_t size)113 ImmutableVector(P producer, size_t size) { 114 if (size) { 115 this->header_ = AllocateFor(size); 116 auto* dst = &(this->header_->item0); 117 auto* dst_limit = dst + size; 118 for (; dst != dst_limit; ++dst) 119 new (dst) T(producer()); 120 } 121 } 122 123 // Container constructor 124 // 125 // This uses std::void_t<> to select container types whose values 126 // are convertible to |const T&|, and which have |begin()| and |size()| 127 // methods. 128 // 129 // This constructor copies items from the constructor into the 130 // ImmutableVector. 131 template <typename C, 132 typename = std::void_t< 133 decltype(static_cast<const T>(*std::declval<C>().begin())), 134 decltype(std::declval<C>().size())>> ImmutableVector(const C & container)135 ImmutableVector(const C& container) { 136 size_t size = container.size(); 137 if (size) { 138 this->header_ = AllocateFor(size); 139 auto src = container.begin(); 140 auto* dst = &(this->header_->item0); 141 auto* dst_limit = dst + size; 142 for (; dst != dst_limit; ++dst, ++src) { 143 new (dst) T(*src); 144 } 145 } 146 } 147 148 // Another container constructor where the items can be moved into 149 // the resulting ImmutableVector. 150 template <typename C, 151 typename = std::void_t< 152 decltype(static_cast<const T>(*std::declval<C>().begin())), 153 decltype(std::declval<C>().size())>> ImmutableVector(C && container)154 ImmutableVector(C&& container) { 155 size_t size = container.size(); 156 if (size) { 157 this->header_ = AllocateFor(size); 158 auto src = container.begin(); 159 auto* dst = &(this->header_->item0); 160 auto* dst_limit = dst + size; 161 for (; dst != dst_limit; ++dst, ++src) 162 new (dst) T(std::move(*src)); 163 } 164 } 165 166 // Initializer-list container. ImmutableVector(std::initializer_list<T> list)167 ImmutableVector(std::initializer_list<T> list) 168 : ImmutableVector(list.begin(), list.size()) {} 169 170 // Copy operations ImmutableVector(const ImmutableVector & other)171 ImmutableVector(const ImmutableVector& other) 172 : ImmutableVectorView<T>( 173 AllocateAndCopyFrom(other.begin(), other.size())) {} 174 175 ImmutableVector& operator=(const ImmutableVector& other) { 176 if (this != &other) { 177 this->~ImmutableVector(); 178 new (this) ImmutableVector(other); 179 } 180 return *this; 181 } 182 183 // Move operations ImmutableVector(ImmutableVector && other)184 ImmutableVector(ImmutableVector&& other) noexcept { 185 this->header_ = other.header_; 186 other.header_ = nullptr; 187 } 188 189 ImmutableVector& operator=(ImmutableVector&& other) noexcept { 190 if (this != &other) { 191 this->~ImmutableVector(); 192 new (this) ImmutableVector(std::move(other)); 193 } 194 return *this; 195 } 196 197 // Destructor ~ImmutableVector()198 ~ImmutableVector() { 199 if (this->header_) { 200 auto* cur = &(this->header_->item0); 201 auto* limit = cur + this->size(); 202 while (cur != limit) { 203 (*cur).~T(); 204 cur++; 205 } 206 Allocator::Free(this->header_); 207 } 208 } 209 210 protected: 211 friend class ImmutableVectorView<T>; 212 213 using Header = typename ImmutableVectorView<T>::Header; 214 215 // Don't use std::max() here to avoid including <algorithm> which is massive. 216 static constexpr size_t kAlignment = alignof(T) > alignof(Header) 217 ? alignof(T) 218 : alignof(Header); 219 220 using Allocator = AlignedAlloc<kAlignment>; 221 AllocateFor(size_t count)222 static Header* AllocateFor(size_t count) { 223 if (!count) 224 return nullptr; 225 226 auto* header = reinterpret_cast<Header*>( 227 Allocator::Alloc(sizeof(Header) + (count - 1u) * sizeof(T))); 228 header->size = count; 229 return header; 230 } 231 AllocateAndCopyFrom(const T * begin,size_t size)232 static Header* AllocateAndCopyFrom(const T* begin, size_t size) { 233 Header* header = AllocateFor(size); 234 if (size) { 235 T* dst = &(header->item0); 236 T* limit = dst + size; 237 for (; dst != limit; ++dst, ++begin) 238 new (dst) T(*begin); 239 } 240 return header; 241 } 242 }; 243 244 #endif // TOOLS_GN_IMMUTABLE_VECTOR_H_ 245