blob: 70e94ad5e18255de33abac3f08a483563aceb526 [file] [log] [blame]
Austin Schuh36244a12019-09-21 17:52:38 -07001// Copyright 2018 The Abseil Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// -----------------------------------------------------------------------------
16// File: fixed_array.h
17// -----------------------------------------------------------------------------
18//
19// A `FixedArray<T>` represents a non-resizable array of `T` where the length of
20// the array can be determined at run-time. It is a good replacement for
21// non-standard and deprecated uses of `alloca()` and variable length arrays
22// within the GCC extension. (See
23// https://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html).
24//
25// `FixedArray` allocates small arrays inline, keeping performance fast by
26// avoiding heap operations. It also helps reduce the chances of
27// accidentally overflowing your stack if large input is passed to
28// your function.
29
30#ifndef ABSL_CONTAINER_FIXED_ARRAY_H_
31#define ABSL_CONTAINER_FIXED_ARRAY_H_
32
33#include <algorithm>
34#include <cassert>
35#include <cstddef>
36#include <initializer_list>
37#include <iterator>
38#include <limits>
39#include <memory>
40#include <new>
41#include <type_traits>
42
43#include "absl/algorithm/algorithm.h"
44#include "absl/base/dynamic_annotations.h"
45#include "absl/base/internal/throw_delegate.h"
46#include "absl/base/macros.h"
47#include "absl/base/optimization.h"
48#include "absl/base/port.h"
49#include "absl/container/internal/compressed_tuple.h"
50#include "absl/memory/memory.h"
51
52namespace absl {
53
54constexpr static auto kFixedArrayUseDefault = static_cast<size_t>(-1);
55
56// -----------------------------------------------------------------------------
57// FixedArray
58// -----------------------------------------------------------------------------
59//
60// A `FixedArray` provides a run-time fixed-size array, allocating a small array
61// inline for efficiency.
62//
63// Most users should not specify an `inline_elements` argument and let
64// `FixedArray` automatically determine the number of elements
65// to store inline based on `sizeof(T)`. If `inline_elements` is specified, the
66// `FixedArray` implementation will use inline storage for arrays with a
67// length <= `inline_elements`.
68//
69// Note that a `FixedArray` constructed with a `size_type` argument will
70// default-initialize its values by leaving trivially constructible types
71// uninitialized (e.g. int, int[4], double), and others default-constructed.
72// This matches the behavior of c-style arrays and `std::array`, but not
73// `std::vector`.
74//
75// Note that `FixedArray` does not provide a public allocator; if it requires a
76// heap allocation, it will do so with global `::operator new[]()` and
77// `::operator delete[]()`, even if T provides class-scope overrides for these
78// operators.
79template <typename T, size_t N = kFixedArrayUseDefault,
80 typename A = std::allocator<T>>
81class FixedArray {
82 static_assert(!std::is_array<T>::value || std::extent<T>::value > 0,
83 "Arrays with unknown bounds cannot be used with FixedArray.");
84
85 static constexpr size_t kInlineBytesDefault = 256;
86
87 using AllocatorTraits = std::allocator_traits<A>;
88 // std::iterator_traits isn't guaranteed to be SFINAE-friendly until C++17,
89 // but this seems to be mostly pedantic.
90 template <typename Iterator>
91 using EnableIfForwardIterator = absl::enable_if_t<std::is_convertible<
92 typename std::iterator_traits<Iterator>::iterator_category,
93 std::forward_iterator_tag>::value>;
94 static constexpr bool NoexceptCopyable() {
95 return std::is_nothrow_copy_constructible<StorageElement>::value &&
96 absl::allocator_is_nothrow<allocator_type>::value;
97 }
98 static constexpr bool NoexceptMovable() {
99 return std::is_nothrow_move_constructible<StorageElement>::value &&
100 absl::allocator_is_nothrow<allocator_type>::value;
101 }
102 static constexpr bool DefaultConstructorIsNonTrivial() {
103 return !absl::is_trivially_default_constructible<StorageElement>::value;
104 }
105
106 public:
107 using allocator_type = typename AllocatorTraits::allocator_type;
108 using value_type = typename allocator_type::value_type;
109 using pointer = typename allocator_type::pointer;
110 using const_pointer = typename allocator_type::const_pointer;
111 using reference = typename allocator_type::reference;
112 using const_reference = typename allocator_type::const_reference;
113 using size_type = typename allocator_type::size_type;
114 using difference_type = typename allocator_type::difference_type;
115 using iterator = pointer;
116 using const_iterator = const_pointer;
117 using reverse_iterator = std::reverse_iterator<iterator>;
118 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
119
120 static constexpr size_type inline_elements =
121 (N == kFixedArrayUseDefault ? kInlineBytesDefault / sizeof(value_type)
122 : static_cast<size_type>(N));
123
124 FixedArray(
125 const FixedArray& other,
126 const allocator_type& a = allocator_type()) noexcept(NoexceptCopyable())
127 : FixedArray(other.begin(), other.end(), a) {}
128
129 FixedArray(
130 FixedArray&& other,
131 const allocator_type& a = allocator_type()) noexcept(NoexceptMovable())
132 : FixedArray(std::make_move_iterator(other.begin()),
133 std::make_move_iterator(other.end()), a) {}
134
135 // Creates an array object that can store `n` elements.
136 // Note that trivially constructible elements will be uninitialized.
137 explicit FixedArray(size_type n, const allocator_type& a = allocator_type())
138 : storage_(n, a) {
139 if (DefaultConstructorIsNonTrivial()) {
140 memory_internal::ConstructRange(storage_.alloc(), storage_.begin(),
141 storage_.end());
142 }
143 }
144
145 // Creates an array initialized with `n` copies of `val`.
146 FixedArray(size_type n, const value_type& val,
147 const allocator_type& a = allocator_type())
148 : storage_(n, a) {
149 memory_internal::ConstructRange(storage_.alloc(), storage_.begin(),
150 storage_.end(), val);
151 }
152
153 // Creates an array initialized with the size and contents of `init_list`.
154 FixedArray(std::initializer_list<value_type> init_list,
155 const allocator_type& a = allocator_type())
156 : FixedArray(init_list.begin(), init_list.end(), a) {}
157
158 // Creates an array initialized with the elements from the input
159 // range. The array's size will always be `std::distance(first, last)`.
160 // REQUIRES: Iterator must be a forward_iterator or better.
161 template <typename Iterator, EnableIfForwardIterator<Iterator>* = nullptr>
162 FixedArray(Iterator first, Iterator last,
163 const allocator_type& a = allocator_type())
164 : storage_(std::distance(first, last), a) {
165 memory_internal::CopyRange(storage_.alloc(), storage_.begin(), first, last);
166 }
167
168 ~FixedArray() noexcept {
169 for (auto* cur = storage_.begin(); cur != storage_.end(); ++cur) {
170 AllocatorTraits::destroy(storage_.alloc(), cur);
171 }
172 }
173
174 // Assignments are deleted because they break the invariant that the size of a
175 // `FixedArray` never changes.
176 void operator=(FixedArray&&) = delete;
177 void operator=(const FixedArray&) = delete;
178
179 // FixedArray::size()
180 //
181 // Returns the length of the fixed array.
182 size_type size() const { return storage_.size(); }
183
184 // FixedArray::max_size()
185 //
186 // Returns the largest possible value of `std::distance(begin(), end())` for a
187 // `FixedArray<T>`. This is equivalent to the most possible addressable bytes
188 // over the number of bytes taken by T.
189 constexpr size_type max_size() const {
190 return (std::numeric_limits<difference_type>::max)() / sizeof(value_type);
191 }
192
193 // FixedArray::empty()
194 //
195 // Returns whether or not the fixed array is empty.
196 bool empty() const { return size() == 0; }
197
198 // FixedArray::memsize()
199 //
200 // Returns the memory size of the fixed array in bytes.
201 size_t memsize() const { return size() * sizeof(value_type); }
202
203 // FixedArray::data()
204 //
205 // Returns a const T* pointer to elements of the `FixedArray`. This pointer
206 // can be used to access (but not modify) the contained elements.
207 const_pointer data() const { return AsValueType(storage_.begin()); }
208
209 // Overload of FixedArray::data() to return a T* pointer to elements of the
210 // fixed array. This pointer can be used to access and modify the contained
211 // elements.
212 pointer data() { return AsValueType(storage_.begin()); }
213
214 // FixedArray::operator[]
215 //
216 // Returns a reference the ith element of the fixed array.
217 // REQUIRES: 0 <= i < size()
218 reference operator[](size_type i) {
219 assert(i < size());
220 return data()[i];
221 }
222
223 // Overload of FixedArray::operator()[] to return a const reference to the
224 // ith element of the fixed array.
225 // REQUIRES: 0 <= i < size()
226 const_reference operator[](size_type i) const {
227 assert(i < size());
228 return data()[i];
229 }
230
231 // FixedArray::at
232 //
233 // Bounds-checked access. Returns a reference to the ith element of the
234 // fiexed array, or throws std::out_of_range
235 reference at(size_type i) {
236 if (ABSL_PREDICT_FALSE(i >= size())) {
237 base_internal::ThrowStdOutOfRange("FixedArray::at failed bounds check");
238 }
239 return data()[i];
240 }
241
242 // Overload of FixedArray::at() to return a const reference to the ith element
243 // of the fixed array.
244 const_reference at(size_type i) const {
245 if (ABSL_PREDICT_FALSE(i >= size())) {
246 base_internal::ThrowStdOutOfRange("FixedArray::at failed bounds check");
247 }
248 return data()[i];
249 }
250
251 // FixedArray::front()
252 //
253 // Returns a reference to the first element of the fixed array.
254 reference front() { return *begin(); }
255
256 // Overload of FixedArray::front() to return a reference to the first element
257 // of a fixed array of const values.
258 const_reference front() const { return *begin(); }
259
260 // FixedArray::back()
261 //
262 // Returns a reference to the last element of the fixed array.
263 reference back() { return *(end() - 1); }
264
265 // Overload of FixedArray::back() to return a reference to the last element
266 // of a fixed array of const values.
267 const_reference back() const { return *(end() - 1); }
268
269 // FixedArray::begin()
270 //
271 // Returns an iterator to the beginning of the fixed array.
272 iterator begin() { return data(); }
273
274 // Overload of FixedArray::begin() to return a const iterator to the
275 // beginning of the fixed array.
276 const_iterator begin() const { return data(); }
277
278 // FixedArray::cbegin()
279 //
280 // Returns a const iterator to the beginning of the fixed array.
281 const_iterator cbegin() const { return begin(); }
282
283 // FixedArray::end()
284 //
285 // Returns an iterator to the end of the fixed array.
286 iterator end() { return data() + size(); }
287
288 // Overload of FixedArray::end() to return a const iterator to the end of the
289 // fixed array.
290 const_iterator end() const { return data() + size(); }
291
292 // FixedArray::cend()
293 //
294 // Returns a const iterator to the end of the fixed array.
295 const_iterator cend() const { return end(); }
296
297 // FixedArray::rbegin()
298 //
299 // Returns a reverse iterator from the end of the fixed array.
300 reverse_iterator rbegin() { return reverse_iterator(end()); }
301
302 // Overload of FixedArray::rbegin() to return a const reverse iterator from
303 // the end of the fixed array.
304 const_reverse_iterator rbegin() const {
305 return const_reverse_iterator(end());
306 }
307
308 // FixedArray::crbegin()
309 //
310 // Returns a const reverse iterator from the end of the fixed array.
311 const_reverse_iterator crbegin() const { return rbegin(); }
312
313 // FixedArray::rend()
314 //
315 // Returns a reverse iterator from the beginning of the fixed array.
316 reverse_iterator rend() { return reverse_iterator(begin()); }
317
318 // Overload of FixedArray::rend() for returning a const reverse iterator
319 // from the beginning of the fixed array.
320 const_reverse_iterator rend() const {
321 return const_reverse_iterator(begin());
322 }
323
324 // FixedArray::crend()
325 //
326 // Returns a reverse iterator from the beginning of the fixed array.
327 const_reverse_iterator crend() const { return rend(); }
328
329 // FixedArray::fill()
330 //
331 // Assigns the given `value` to all elements in the fixed array.
332 void fill(const value_type& val) { std::fill(begin(), end(), val); }
333
334 // Relational operators. Equality operators are elementwise using
335 // `operator==`, while order operators order FixedArrays lexicographically.
336 friend bool operator==(const FixedArray& lhs, const FixedArray& rhs) {
337 return absl::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
338 }
339
340 friend bool operator!=(const FixedArray& lhs, const FixedArray& rhs) {
341 return !(lhs == rhs);
342 }
343
344 friend bool operator<(const FixedArray& lhs, const FixedArray& rhs) {
345 return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(),
346 rhs.end());
347 }
348
349 friend bool operator>(const FixedArray& lhs, const FixedArray& rhs) {
350 return rhs < lhs;
351 }
352
353 friend bool operator<=(const FixedArray& lhs, const FixedArray& rhs) {
354 return !(rhs < lhs);
355 }
356
357 friend bool operator>=(const FixedArray& lhs, const FixedArray& rhs) {
358 return !(lhs < rhs);
359 }
360
361 template <typename H>
362 friend H AbslHashValue(H h, const FixedArray& v) {
363 return H::combine(H::combine_contiguous(std::move(h), v.data(), v.size()),
364 v.size());
365 }
366
367 private:
368 // StorageElement
369 //
370 // For FixedArrays with a C-style-array value_type, StorageElement is a POD
371 // wrapper struct called StorageElementWrapper that holds the value_type
372 // instance inside. This is needed for construction and destruction of the
373 // entire array regardless of how many dimensions it has. For all other cases,
374 // StorageElement is just an alias of value_type.
375 //
376 // Maintainer's Note: The simpler solution would be to simply wrap value_type
377 // in a struct whether it's an array or not. That causes some paranoid
378 // diagnostics to misfire, believing that 'data()' returns a pointer to a
379 // single element, rather than the packed array that it really is.
380 // e.g.:
381 //
382 // FixedArray<char> buf(1);
383 // sprintf(buf.data(), "foo");
384 //
385 // error: call to int __builtin___sprintf_chk(etc...)
386 // will always overflow destination buffer [-Werror]
387 //
388 template <typename OuterT, typename InnerT = absl::remove_extent_t<OuterT>,
389 size_t InnerN = std::extent<OuterT>::value>
390 struct StorageElementWrapper {
391 InnerT array[InnerN];
392 };
393
394 using StorageElement =
395 absl::conditional_t<std::is_array<value_type>::value,
396 StorageElementWrapper<value_type>, value_type>;
397
398 static pointer AsValueType(pointer ptr) { return ptr; }
399 static pointer AsValueType(StorageElementWrapper<value_type>* ptr) {
400 return std::addressof(ptr->array);
401 }
402
403 static_assert(sizeof(StorageElement) == sizeof(value_type), "");
404 static_assert(alignof(StorageElement) == alignof(value_type), "");
405
406 class NonEmptyInlinedStorage {
407 public:
408 StorageElement* data() { return reinterpret_cast<StorageElement*>(buff_); }
409 void AnnotateConstruct(size_type n);
410 void AnnotateDestruct(size_type n);
411
412#ifdef ADDRESS_SANITIZER
413 void* RedzoneBegin() { return &redzone_begin_; }
414 void* RedzoneEnd() { return &redzone_end_ + 1; }
415#endif // ADDRESS_SANITIZER
416
417 private:
418 ADDRESS_SANITIZER_REDZONE(redzone_begin_);
419 alignas(StorageElement) char buff_[sizeof(StorageElement[inline_elements])];
420 ADDRESS_SANITIZER_REDZONE(redzone_end_);
421 };
422
423 class EmptyInlinedStorage {
424 public:
425 StorageElement* data() { return nullptr; }
426 void AnnotateConstruct(size_type) {}
427 void AnnotateDestruct(size_type) {}
428 };
429
430 using InlinedStorage =
431 absl::conditional_t<inline_elements == 0, EmptyInlinedStorage,
432 NonEmptyInlinedStorage>;
433
434 // Storage
435 //
436 // An instance of Storage manages the inline and out-of-line memory for
437 // instances of FixedArray. This guarantees that even when construction of
438 // individual elements fails in the FixedArray constructor body, the
439 // destructor for Storage will still be called and out-of-line memory will be
440 // properly deallocated.
441 //
442 class Storage : public InlinedStorage {
443 public:
444 Storage(size_type n, const allocator_type& a)
445 : size_alloc_(n, a), data_(InitializeData()) {}
446
447 ~Storage() noexcept {
448 if (UsingInlinedStorage(size())) {
449 InlinedStorage::AnnotateDestruct(size());
450 } else {
451 AllocatorTraits::deallocate(alloc(), AsValueType(begin()), size());
452 }
453 }
454
455 size_type size() const { return size_alloc_.template get<0>(); }
456 StorageElement* begin() const { return data_; }
457 StorageElement* end() const { return begin() + size(); }
458 allocator_type& alloc() { return size_alloc_.template get<1>(); }
459
460 private:
461 static bool UsingInlinedStorage(size_type n) {
462 return n <= inline_elements;
463 }
464
465 StorageElement* InitializeData() {
466 if (UsingInlinedStorage(size())) {
467 InlinedStorage::AnnotateConstruct(size());
468 return InlinedStorage::data();
469 } else {
470 return reinterpret_cast<StorageElement*>(
471 AllocatorTraits::allocate(alloc(), size()));
472 }
473 }
474
475 // `CompressedTuple` takes advantage of EBCO for stateless `allocator_type`s
476 container_internal::CompressedTuple<size_type, allocator_type> size_alloc_;
477 StorageElement* data_;
478 };
479
480 Storage storage_;
481};
482
483template <typename T, size_t N, typename A>
484constexpr size_t FixedArray<T, N, A>::kInlineBytesDefault;
485
486template <typename T, size_t N, typename A>
487constexpr typename FixedArray<T, N, A>::size_type
488 FixedArray<T, N, A>::inline_elements;
489
490template <typename T, size_t N, typename A>
491void FixedArray<T, N, A>::NonEmptyInlinedStorage::AnnotateConstruct(
492 typename FixedArray<T, N, A>::size_type n) {
493#ifdef ADDRESS_SANITIZER
494 if (!n) return;
495 ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), RedzoneEnd(), data() + n);
496 ANNOTATE_CONTIGUOUS_CONTAINER(RedzoneBegin(), data(), data(), RedzoneBegin());
497#endif // ADDRESS_SANITIZER
498 static_cast<void>(n); // Mark used when not in asan mode
499}
500
501template <typename T, size_t N, typename A>
502void FixedArray<T, N, A>::NonEmptyInlinedStorage::AnnotateDestruct(
503 typename FixedArray<T, N, A>::size_type n) {
504#ifdef ADDRESS_SANITIZER
505 if (!n) return;
506 ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), data() + n, RedzoneEnd());
507 ANNOTATE_CONTIGUOUS_CONTAINER(RedzoneBegin(), data(), RedzoneBegin(), data());
508#endif // ADDRESS_SANITIZER
509 static_cast<void>(n); // Mark used when not in asan mode
510}
511} // namespace absl
512
513#endif // ABSL_CONTAINER_FIXED_ARRAY_H_