diff --git a/webrtc/base/array_view.h b/webrtc/base/array_view.h index aa73ef72f6..7a0bb28954 100644 --- a/webrtc/base/array_view.h +++ b/webrtc/base/array_view.h @@ -31,9 +31,9 @@ namespace rtc { // std::vector, rtc::Buffer, ...), but it's error-prone because the caller has // to correctly specify the array length: // -// Contains17(arr, arraysize(arr)); // C array -// Contains17(&arr[0], arr.size()); // std::vector -// Contains17(arr, size); // pointer + size +// Contains17(arr, arraysize(arr)); // C array +// Contains17(arr.data(), arr.size()); // std::vector +// Contains17(arr, size); // pointer + size // ... // // It's also kind of messy to have two separate arguments for what is @@ -60,6 +60,10 @@ namespace rtc { // Contains17(nullptr); // nullptr -> empty ArrayView // ... // +// ArrayView stores both a pointer and a size, but you may also use +// ArrayView, which has a size that's fixed at compile time (which means +// it only has to store the pointer). +// // One important point is that ArrayView and ArrayView are // different types, which allow and don't allow mutation of the array elements, // respectively. The implicit conversions work just like you'd hope, so that @@ -68,81 +72,177 @@ namespace rtc { // (ArrayView itself can be the source type in such conversions, so // ArrayView will convert to ArrayView.) // -// Note: ArrayView is tiny (just a pointer and a count) and trivially copyable, -// so it's probably cheaper to pass it by value than by const reference. +// Note: ArrayView is tiny (just a pointer and a count if variable-sized, just +// a pointer if fix-sized) and trivially copyable, so it's probably cheaper to +// pass it by value than by const reference. + +namespace impl { + +// Magic constant for indicating that the size of an ArrayView is variable +// instead of fixed. +enum : std::ptrdiff_t { kArrayViewVarSize = -4711 }; + +// Base class for ArrayViews of fixed nonzero size. +template +class ArrayViewBase { + static_assert(Size > 0, "ArrayView size must be variable or non-negative"); + + public: + ArrayViewBase(T* data, size_t size) : data_(data) {} + + static constexpr size_t size() { return Size; } + static constexpr bool empty() { return false; } + T* data() const { return data_; } + + protected: + static constexpr bool fixed_size() { return true; } + + private: + T* data_; +}; + +// Specialized base class for ArrayViews of fixed zero size. template -class ArrayView final { +class ArrayViewBase { + public: + explicit ArrayViewBase(T* data, size_t size) {} + + static constexpr size_t size() { return 0; } + static constexpr bool empty() { return true; } + T* data() const { return nullptr; } + + protected: + static constexpr bool fixed_size() { return true; } +}; + +// Specialized base class for ArrayViews of variable size. +template +class ArrayViewBase { + public: + ArrayViewBase(T* data, size_t size) + : data_(size == 0 ? nullptr : data), size_(size) {} + + size_t size() const { return size_; } + bool empty() const { return size_ == 0; } + T* data() const { return data_; } + + protected: + static constexpr bool fixed_size() { return false; } + + private: + T* data_; + size_t size_; +}; + +} // namespace impl + +template +class ArrayView final : public impl::ArrayViewBase { public: using value_type = T; using const_iterator = const T*; - // Construct an empty ArrayView. - ArrayView() : ArrayView(static_cast(nullptr), 0) {} - ArrayView(std::nullptr_t) : ArrayView() {} - - // Construct an ArrayView for a (pointer,size) pair. + // Construct an ArrayView from a pointer and a length. template ArrayView(U* data, size_t size) - : data_(size == 0 ? nullptr : data), size_(size) { - CheckInvariant(); + : impl::ArrayViewBase::ArrayViewBase(data, size) { + RTC_DCHECK_EQ(size == 0 ? nullptr : data, this->data()); + RTC_DCHECK_EQ(size, this->size()); + RTC_DCHECK_EQ(!this->data(), + this->size() == 0); // data is null iff size == 0. } - // Construct an ArrayView for an array. - template - ArrayView(U (&array)[N]) : ArrayView(&array[0], N) {} + // Construct an empty ArrayView. Note that fixed-size ArrayViews of size > 0 + // cannot be empty. + ArrayView() : ArrayView(nullptr, 0) {} + ArrayView(std::nullptr_t) : ArrayView() {} + ArrayView(std::nullptr_t, size_t size) + : ArrayView(static_cast(nullptr), size) { + static_assert(Size == 0 || Size == impl::kArrayViewVarSize, ""); + RTC_DCHECK_EQ(0, size); + } - // Construct an ArrayView for any type U that has a size() method whose - // return value converts implicitly to size_t, and a data() method whose - // return value converts implicitly to T*. In particular, this means we allow - // conversion from ArrayView to ArrayView, but not the other way - // around. Other allowed conversions include std::vector to ArrayView - // or ArrayView, const std::vector to ArrayView, and - // rtc::Buffer to ArrayView (with the same const behavior as - // std::vector). + // Construct an ArrayView from an array. + template + ArrayView(U (&array)[N]) : ArrayView(array, N) { + static_assert(Size == N || Size == impl::kArrayViewVarSize, + "Array size must match ArrayView size"); + } + + // (Only if size is fixed.) Construct an ArrayView from any type U that has a + // static constexpr size() method whose return value is equal to Size, and a + // data() method whose return value converts implicitly to T*. In particular, + // this means we allow conversion from ArrayView to ArrayView, but not the other way around. We also don't allow conversion from + // ArrayView to ArrayView, or from ArrayView to ArrayView when M != N. + template ::value>::type* = nullptr> + ArrayView(U& u) : ArrayView(u.data(), u.size()) { + static_assert(U::size() == Size, "Sizes must match exactly"); + } + + // (Only if size is variable.) Construct an ArrayView from any type U that + // has a size() method whose return value converts implicitly to size_t, and + // a data() method whose return value converts implicitly to T*. In + // particular, this means we allow conversion from ArrayView to + // ArrayView, but not the other way around. Other allowed + // conversions include + // ArrayView to ArrayView or ArrayView, + // std::vector to ArrayView or ArrayView, + // const std::vector to ArrayView, + // rtc::Buffer to ArrayView or ArrayView, and + // const rtc::Buffer to ArrayView. template < typename U, - typename std::enable_if::value>::type* = nullptr> + typename std::enable_if::value>::type* = nullptr> ArrayView(U& u) : ArrayView(u.data(), u.size()) {} - // Indexing, size, and iteration. These allow mutation even if the ArrayView - // is const, because the ArrayView doesn't own the array. (To prevent - // mutation, use ArrayView.) - size_t size() const { return size_; } - bool empty() const { return size_ == 0; } - T* data() const { return data_; } + // Indexing and iteration. These allow mutation even if the ArrayView is + // const, because the ArrayView doesn't own the array. (To prevent mutation, + // use a const element type.) T& operator[](size_t idx) const { - RTC_DCHECK_LT(idx, size_); - RTC_DCHECK(data_); // Follows from size_ > idx and the class invariant. - return data_[idx]; + RTC_DCHECK_LT(idx, this->size()); + RTC_DCHECK(this->data()); + return this->data()[idx]; } - T* begin() const { return data_; } - T* end() const { return data_ + size_; } - const T* cbegin() const { return data_; } - const T* cend() const { return data_ + size_; } + T* begin() const { return this->data(); } + T* end() const { return this->data() + this->size(); } + const T* cbegin() const { return this->data(); } + const T* cend() const { return this->data() + this->size(); } - ArrayView subview(size_t offset, size_t size) const { - if (offset >= size_) - return ArrayView(); - return ArrayView(data_ + offset, std::min(size, size_ - offset)); + ArrayView subview(size_t offset, size_t size) const { + return offset < this->size() + ? ArrayView(this->data() + offset, + std::min(size, this->size() - offset)) + : ArrayView(); } - ArrayView subview(size_t offset) const { return subview(offset, size_); } - - // Comparing two ArrayViews compares their (pointer,size) pairs; it does - // *not* dereference the pointers. - friend bool operator==(const ArrayView& a, const ArrayView& b) { - return a.data_ == b.data_ && a.size_ == b.size_; + ArrayView subview(size_t offset) const { + return subview(offset, this->size()); } - friend bool operator!=(const ArrayView& a, const ArrayView& b) { - return !(a == b); - } - - private: - // Invariant: !data_ iff size_ == 0. - void CheckInvariant() const { RTC_DCHECK_EQ(!data_, size_ == 0); } - T* data_; - size_t size_; }; +// Comparing two ArrayViews compares their (pointer,size) pairs; it does *not* +// dereference the pointers. +template +bool operator==(const ArrayView& a, const ArrayView& b) { + return a.data() == b.data() && a.size() == b.size(); +} +template +bool operator!=(const ArrayView& a, const ArrayView& b) { + return !(a == b); +} + +// Variable-size ArrayViews are the size of two pointers; fixed-size ArrayViews +// are the size of one pointer. (And as a special case, fixed-size ArrayViews +// of size 0 require no storage.) +static_assert(sizeof(ArrayView) == 2 * sizeof(int*), ""); +static_assert(sizeof(ArrayView) == sizeof(int*), ""); +static_assert(std::is_empty>::value, ""); + template inline ArrayView MakeArrayView(T* data, size_t size) { return ArrayView(data, size); diff --git a/webrtc/base/array_view_unittest.cc b/webrtc/base/array_view_unittest.cc index f7fe9fa36b..035f25ca49 100644 --- a/webrtc/base/array_view_unittest.cc +++ b/webrtc/base/array_view_unittest.cc @@ -44,15 +44,27 @@ TEST(ArrayViewTest, TestConstructFromPtrAndArray) { ArrayView y = arr; EXPECT_EQ(6u, y.size()); EXPECT_EQ(arr, y.data()); + ArrayView yf = arr; + static_assert(yf.size() == 6, ""); + EXPECT_EQ(arr, yf.data()); ArrayView z(arr + 1, 3); EXPECT_EQ(3u, z.size()); EXPECT_EQ(arr + 1, z.data()); + ArrayView zf(arr + 1, 3); + static_assert(zf.size() == 3, ""); + EXPECT_EQ(arr + 1, zf.data()); ArrayView w(arr, 2); EXPECT_EQ(2u, w.size()); EXPECT_EQ(arr, w.data()); + ArrayView wf(arr, 2); + static_assert(wf.size() == 2, ""); + EXPECT_EQ(arr, wf.data()); ArrayView q(arr, 0); EXPECT_EQ(0u, q.size()); EXPECT_EQ(nullptr, q.data()); + ArrayView qf(arr, 0); + static_assert(qf.size() == 0, ""); + EXPECT_EQ(nullptr, qf.data()); #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) // DCHECK error (nullptr with nonzero size). EXPECT_DEATH(ArrayView(static_cast(nullptr), 5), ""); @@ -62,7 +74,7 @@ TEST(ArrayViewTest, TestConstructFromPtrAndArray) { // ArrayView n(arr + 2, 2); } -TEST(ArrayViewTest, TestCopyConstructor) { +TEST(ArrayViewTest, TestCopyConstructorVariable) { char arr[] = "Arrr!"; ArrayView x = arr; EXPECT_EQ(6u, x.size()); @@ -79,7 +91,38 @@ TEST(ArrayViewTest, TestCopyConstructor) { // ArrayView v = z; // Compile error, because can't drop const. } -TEST(ArrayViewTest, TestCopyAssignment) { +TEST(ArrayViewTest, TestCopyConstructorFixed) { + char arr[] = "Arrr!"; + ArrayView x = arr; + static_assert(x.size() == 6, ""); + EXPECT_EQ(arr, x.data()); + + // Copy fixed -> fixed. + ArrayView y = x; // Copy non-const -> non-const. + static_assert(y.size() == 6, ""); + EXPECT_EQ(arr, y.data()); + ArrayView z = x; // Copy non-const -> const. + static_assert(z.size() == 6, ""); + EXPECT_EQ(arr, z.data()); + ArrayView w = z; // Copy const -> const. + static_assert(w.size() == 6, ""); + EXPECT_EQ(arr, w.data()); + // ArrayView v = z; // Compile error, because can't drop const. + + // Copy fixed -> variable. + ArrayView yv = x; // Copy non-const -> non-const. + EXPECT_EQ(6u, yv.size()); + EXPECT_EQ(arr, yv.data()); + ArrayView zv = x; // Copy non-const -> const. + EXPECT_EQ(6u, zv.size()); + EXPECT_EQ(arr, zv.data()); + ArrayView wv = z; // Copy const -> const. + EXPECT_EQ(6u, wv.size()); + EXPECT_EQ(arr, wv.data()); + // ArrayView vv = z; // Compile error, because can't drop const. +} + +TEST(ArrayViewTest, TestCopyAssignmentVariable) { char arr[] = "Arrr!"; ArrayView x(arr); EXPECT_EQ(6u, x.size()); @@ -100,6 +143,42 @@ TEST(ArrayViewTest, TestCopyAssignment) { // v = z; // Compile error, because can't drop const. } +TEST(ArrayViewTest, TestCopyAssignmentFixed) { + char arr[] = "Arrr!"; + char init[] = "Init!"; + ArrayView x(arr); + EXPECT_EQ(arr, x.data()); + + // Copy fixed -> fixed. + ArrayView y(init); + y = x; // Copy non-const -> non-const. + EXPECT_EQ(arr, y.data()); + ArrayView z(init); + z = x; // Copy non-const -> const. + EXPECT_EQ(arr, z.data()); + ArrayView w(init); + w = z; // Copy const -> const. + EXPECT_EQ(arr, w.data()); + // ArrayView v(init); + // v = z; // Compile error, because can't drop const. + + // Copy fixed -> variable. + ArrayView yv; + yv = x; // Copy non-const -> non-const. + EXPECT_EQ(6u, yv.size()); + EXPECT_EQ(arr, yv.data()); + ArrayView zv; + zv = x; // Copy non-const -> const. + EXPECT_EQ(6u, zv.size()); + EXPECT_EQ(arr, zv.data()); + ArrayView wv; + wv = z; // Copy const -> const. + EXPECT_EQ(6u, wv.size()); + EXPECT_EQ(arr, wv.data()); + // ArrayView v; + // v = z; // Compile error, because can't drop const. +} + TEST(ArrayViewTest, TestStdVector) { std::vector v; v.push_back(3); @@ -146,7 +225,7 @@ TEST(ArrayViewTest, TestRtcBuffer) { // ArrayView w = cb; // Compile error, because can't drop const. } -TEST(ArrayViewTest, TestSwap) { +TEST(ArrayViewTest, TestSwapVariable) { const char arr[] = "Arrr!"; const char aye[] = "Aye, Cap'n!"; ArrayView x(arr); @@ -165,11 +244,28 @@ TEST(ArrayViewTest, TestSwap) { // swap(x, z); // Compile error, because can't drop const. } +TEST(FixArrayViewTest, TestSwapFixed) { + const char arr[] = "Arr!"; + char aye[] = "Aye!"; + ArrayView x(arr); + EXPECT_EQ(arr, x.data()); + ArrayView y(aye); + EXPECT_EQ(aye, y.data()); + using std::swap; + swap(x, y); + EXPECT_EQ(aye, x.data()); + EXPECT_EQ(arr, y.data()); + // ArrayView z(aye); + // swap(x, z); // Compile error, because can't drop const. + // ArrayView w(aye, 4); + // swap(x, w); // Compile error, because different sizes. +} + TEST(ArrayViewTest, TestIndexing) { char arr[] = "abcdefg"; ArrayView x(arr); const ArrayView y(arr); - ArrayView z(arr); + ArrayView z(arr); EXPECT_EQ(8u, x.size()); EXPECT_EQ(8u, y.size()); EXPECT_EQ(8u, z.size()); @@ -188,18 +284,26 @@ TEST(ArrayViewTest, TestIndexing) { } TEST(ArrayViewTest, TestIterationEmpty) { + // Variable-size. ArrayView>>> av; - EXPECT_FALSE(av.begin()); - EXPECT_FALSE(av.cbegin()); - EXPECT_FALSE(av.end()); - EXPECT_FALSE(av.cend()); + EXPECT_EQ(av.begin(), av.end()); + EXPECT_EQ(av.cbegin(), av.cend()); for (auto& e : av) { EXPECT_TRUE(false); EXPECT_EQ(42u, e.size()); // Dummy use of e to prevent unused var warning. } + + // Fixed-size. + ArrayView>>, 0> af; + EXPECT_EQ(af.begin(), af.end()); + EXPECT_EQ(af.cbegin(), af.cend()); + for (auto& e : af) { + EXPECT_TRUE(false); + EXPECT_EQ(42u, e.size()); // Dummy use of e to prevent unused var warning. + } } -TEST(ArrayViewTest, TestIteration) { +TEST(ArrayViewTest, TestIterationVariable) { char arr[] = "Arrr!"; ArrayView av(arr); EXPECT_EQ('A', *av.begin()); @@ -220,23 +324,57 @@ TEST(ArrayViewTest, TestIteration) { } } +TEST(ArrayViewTest, TestIterationFixed) { + char arr[] = "Arrr!"; + ArrayView av(arr); + EXPECT_EQ('A', *av.begin()); + EXPECT_EQ('A', *av.cbegin()); + EXPECT_EQ('\0', *(av.end() - 1)); + EXPECT_EQ('\0', *(av.cend() - 1)); + char i = 0; + for (auto& e : av) { + EXPECT_EQ(arr + i, &e); + e = 's' + i; + ++i; + } + i = 0; + for (auto& e : ArrayView(av)) { + EXPECT_EQ(arr + i, &e); + // e = 'q' + i; // Compile error, because e is a const char&. + ++i; + } +} + TEST(ArrayViewTest, TestEmpty) { EXPECT_TRUE(ArrayView().empty()); const int a[] = {1, 2, 3}; EXPECT_FALSE(ArrayView(a).empty()); + + static_assert(ArrayView::empty(), ""); + static_assert(!ArrayView::empty(), ""); } TEST(ArrayViewTest, TestCompare) { int a[] = {1, 2, 3}; int b[] = {1, 2, 3}; + EXPECT_EQ(ArrayView(a), ArrayView(a)); + EXPECT_EQ((ArrayView(a)), (ArrayView(a))); + EXPECT_EQ(ArrayView(a), (ArrayView(a))); EXPECT_EQ(ArrayView(), ArrayView()); + EXPECT_EQ(ArrayView(), ArrayView(a, 0)); + EXPECT_EQ(ArrayView(a, 0), ArrayView(b, 0)); + EXPECT_EQ((ArrayView(a, 0)), ArrayView()); + EXPECT_NE(ArrayView(a), ArrayView(b)); + EXPECT_NE((ArrayView(a)), (ArrayView(b))); + EXPECT_NE((ArrayView(a)), ArrayView(b)); EXPECT_NE(ArrayView(a), ArrayView()); EXPECT_NE(ArrayView(a), ArrayView(a, 2)); + EXPECT_NE((ArrayView(a)), (ArrayView(a, 2))); } -TEST(ArrayViewTest, TestSubView) { +TEST(ArrayViewTest, TestSubViewVariable) { int a[] = {1, 2, 3}; ArrayView av(a); @@ -253,4 +391,21 @@ TEST(ArrayViewTest, TestSubView) { EXPECT_THAT(av.subview(1, 3), ElementsAre(2, 3)); } +TEST(ArrayViewTest, TestSubViewFixed) { + int a[] = {1, 2, 3}; + ArrayView av(a); + + EXPECT_EQ(av.subview(0), av); + + EXPECT_THAT(av.subview(1), ElementsAre(2, 3)); + EXPECT_THAT(av.subview(2), ElementsAre(3)); + EXPECT_THAT(av.subview(3), IsEmpty()); + EXPECT_THAT(av.subview(4), IsEmpty()); + + EXPECT_THAT(av.subview(1, 0), IsEmpty()); + EXPECT_THAT(av.subview(1, 1), ElementsAre(2)); + EXPECT_THAT(av.subview(1, 2), ElementsAre(2, 3)); + EXPECT_THAT(av.subview(1, 3), ElementsAre(2, 3)); +} + } // namespace rtc