summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/rtc_base/bounded_inline_vector_impl.h
blob: 3539ace5bcd86d1a6eebd9f559b625dfb10cc8ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*
 *  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_