| /* |
| * Copyright 2020 The WebRTC Project Authors. All rights reserved. |
| * |
| * Use of this source code is governed by a BSD-style license |
| * that can be found in the LICENSE file in the root of the source |
| * tree. An additional intellectual property rights grant can be found |
| * in the file PATENTS. All contributing project authors may |
| * be found in the AUTHORS file in the root of the source tree. |
| */ |
| |
| #ifndef RTC_BASE_BOUNDED_INLINE_VECTOR_IMPL_H_ |
| #define RTC_BASE_BOUNDED_INLINE_VECTOR_IMPL_H_ |
| |
| #include <stdint.h> |
| |
| #include <cstring> |
| #include <memory> |
| #include <type_traits> |
| #include <utility> |
| |
| namespace webrtc { |
| namespace bounded_inline_vector_impl { |
| |
| template <bool...> |
| struct BoolPack; |
| |
| // Tests if all its parameters (x0, x1, ..., xn) are true. The implementation |
| // checks whether (x0, x1, ..., xn, true) == (true, x0, x1, ..., xn), which is |
| // true iff true == x0 && x0 == x1 && x1 == x2 ... && xn-1 == xn && xn == true. |
| template <bool... Bs> |
| using AllTrue = std::is_same<BoolPack<Bs..., true>, BoolPack<true, Bs...>>; |
| |
| template <typename To, typename... Froms> |
| using AllConvertible = AllTrue<std::is_convertible<Froms, To>::value...>; |
| |
| // Initializes part of an uninitialized array. Unlike normal array |
| // initialization, does not zero the remaining array elements. Caller is |
| // responsible for ensuring that there is enough space in `data`. |
| template <typename T> |
| void InitializeElements(T* data) {} |
| template <typename T, typename U, typename... Us> |
| void InitializeElements(T* data, U&& element, Us&&... elements) { |
| // Placement new, because we construct a new object in uninitialized memory. |
| ::new (data) T(std::forward<U>(element)); |
| InitializeElements(data + 1, std::forward<Us>(elements)...); |
| } |
| |
| // Default initializes uninitialized array elements. |
| // TODO(kwiberg): Replace with std::uninitialized_default_construct_n() (C++17). |
| template <typename T> |
| void DefaultInitializeElements(T* data, int size) { |
| for (int i = 0; i < size; ++i) { |
| // Placement new, because we construct a new object in uninitialized memory. |
| ::new (&data[i]) T; |
| } |
| } |
| |
| // Copies from source to uninitialized destination. Caller is responsible for |
| // ensuring that there is enough space in `dst_data`. |
| template <typename T> |
| void CopyElements(const T* src_data, int src_size, T* dst_data, int* dst_size) { |
| if /*constexpr*/ (std::is_trivially_copy_constructible<T>::value) { |
| std::memcpy(dst_data, src_data, src_size * sizeof(T)); |
| } else { |
| std::uninitialized_copy_n(src_data, src_size, dst_data); |
| } |
| *dst_size = src_size; |
| } |
| |
| // Moves from source to uninitialized destination. Caller is responsible for |
| // ensuring that there is enough space in `dst_data`. |
| template <typename T> |
| void MoveElements(T* src_data, int src_size, T* dst_data, int* dst_size) { |
| if /*constexpr*/ (std::is_trivially_move_constructible<T>::value) { |
| std::memcpy(dst_data, src_data, src_size * sizeof(T)); |
| } else { |
| // TODO(kwiberg): Use std::uninitialized_move_n() instead (C++17). |
| for (int i = 0; i < src_size; ++i) { |
| // Placement new, because we create a new object in uninitialized |
| // memory. |
| ::new (&dst_data[i]) T(std::move(src_data[i])); |
| } |
| } |
| *dst_size = src_size; |
| } |
| |
| // Destroys elements, leaving them uninitialized. |
| template <typename T> |
| void DestroyElements(T* data, int size) { |
| if /*constexpr*/ (!std::is_trivially_destructible<T>::value) { |
| for (int i = 0; i < size; ++i) { |
| data[i].~T(); |
| } |
| } |
| } |
| |
| // If elements are trivial and the total capacity is at most this many bytes, |
| // copy everything instead of just the elements that are in use; this is more |
| // efficient, and makes BoundedInlineVector trivially copyable. |
| static constexpr int kSmallSize = 64; |
| |
| // Storage implementations. |
| // |
| // There are diferent Storage structs for diferent kinds of element types. The |
| // common contract is the following: |
| // |
| // * They have public `size` variables and `data` array members. |
| // |
| // * Their owner is responsible for enforcing the invariant that the first |
| // `size` elements in `data` are initialized, and the remaining elements are |
| // not initialized. |
| // |
| // * They implement default construction, construction with one or more |
| // elements, copy/move construction, copy/move assignment, and destruction; |
| // the owner must ensure that the invariant holds whenever these operations |
| // occur. |
| |
| // Storage implementation for nontrivial element types. |
| template <typename T, |
| int fixed_capacity, |
| bool is_trivial = std::is_trivial<T>::value, |
| bool is_small = (sizeof(T) * fixed_capacity <= kSmallSize)> |
| struct Storage { |
| static_assert(!std::is_trivial<T>::value, ""); |
| |
| template < |
| typename... Ts, |
| typename std::enable_if_t<AllConvertible<T, Ts...>::value>* = nullptr> |
| explicit Storage(Ts&&... elements) : size(sizeof...(Ts)) { |
| InitializeElements(data, std::forward<Ts>(elements)...); |
| } |
| |
| Storage(const Storage& other) { |
| CopyElements(other.data, other.size, data, &size); |
| } |
| |
| Storage(Storage&& other) { |
| MoveElements(other.data, other.size, data, &size); |
| } |
| |
| Storage& operator=(const Storage& other) { |
| if (this != &other) { |
| DestroyElements(data, size); |
| CopyElements(other.data, other.size, data, &size); |
| } |
| return *this; |
| } |
| |
| Storage& operator=(Storage&& other) { |
| DestroyElements(data, size); |
| size = 0; // Needed in case of self assignment. |
| MoveElements(other.data, other.size, data, &size); |
| return *this; |
| } |
| |
| ~Storage() { DestroyElements(data, size); } |
| |
| int size; |
| union { |
| // Since this array is in a union, we get to construct and destroy it |
| // manually. |
| T data[fixed_capacity]; // NOLINT(runtime/arrays) |
| }; |
| }; |
| |
| // Storage implementation for trivial element types when the capacity is small |
| // enough that we can cheaply copy everything. |
| template <typename T, int fixed_capacity> |
| struct Storage<T, fixed_capacity, /*is_trivial=*/true, /*is_small=*/true> { |
| static_assert(std::is_trivial<T>::value, ""); |
| static_assert(sizeof(T) * fixed_capacity <= kSmallSize, ""); |
| |
| template < |
| typename... Ts, |
| typename std::enable_if_t<AllConvertible<T, Ts...>::value>* = nullptr> |
| explicit Storage(Ts&&... elements) : size(sizeof...(Ts)) { |
| InitializeElements(data, std::forward<Ts>(elements)...); |
| } |
| |
| Storage(const Storage&) = default; |
| Storage& operator=(const Storage&) = default; |
| ~Storage() = default; |
| |
| int size; |
| T data[fixed_capacity]; // NOLINT(runtime/arrays) |
| }; |
| |
| // Storage implementation for trivial element types when the capacity is large |
| // enough that we want to avoid copying uninitialized elements. |
| template <typename T, int fixed_capacity> |
| struct Storage<T, fixed_capacity, /*is_trivial=*/true, /*is_small=*/false> { |
| static_assert(std::is_trivial<T>::value, ""); |
| static_assert(sizeof(T) * fixed_capacity > kSmallSize, ""); |
| |
| template < |
| typename... Ts, |
| typename std::enable_if_t<AllConvertible<T, Ts...>::value>* = nullptr> |
| explicit Storage(Ts&&... elements) : size(sizeof...(Ts)) { |
| InitializeElements(data, std::forward<Ts>(elements)...); |
| } |
| |
| Storage(const Storage& other) : size(other.size) { |
| std::memcpy(data, other.data, other.size * sizeof(T)); |
| } |
| |
| Storage& operator=(const Storage& other) { |
| if (this != &other) { |
| size = other.size; |
| std::memcpy(data, other.data, other.size * sizeof(T)); |
| } |
| return *this; |
| } |
| |
| ~Storage() = default; |
| |
| int size; |
| union { |
| T data[fixed_capacity]; // NOLINT(runtime/arrays) |
| }; |
| }; |
| |
| } // namespace bounded_inline_vector_impl |
| } // namespace webrtc |
| |
| #endif // RTC_BASE_BOUNDED_INLINE_VECTOR_IMPL_H_ |