diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn index 825741c6a5..04e57b8124 100644 --- a/src/tint/BUILD.gn +++ b/src/tint/BUILD.gn @@ -564,6 +564,7 @@ libtint_source_set("libtint_core_all_src") { "utils/string.h", "utils/unique_allocator.h", "utils/unique_vector.h", + "utils/vector.h", "writer/append_vector.cc", "writer/append_vector.h", "writer/array_length_from_uniform_options.cc", @@ -1227,6 +1228,7 @@ if (tint_build_unittests) { "utils/transform_test.cc", "utils/unique_allocator_test.cc", "utils/unique_vector_test.cc", + "utils/vector_test.cc", ] } diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt index ec0e78e8e5..ac83e39bed 100644 --- a/src/tint/CMakeLists.txt +++ b/src/tint/CMakeLists.txt @@ -474,6 +474,7 @@ set(TINT_LIB_SRCS utils/string.h utils/unique_allocator.h utils/unique_vector.h + utils/vector.h writer/append_vector.cc writer/append_vector.h writer/array_length_from_uniform_options.cc @@ -857,6 +858,7 @@ if(TINT_BUILD_TESTS) utils/transform_test.cc utils/unique_allocator_test.cc utils/unique_vector_test.cc + utils/vector_test.cc writer/append_vector_test.cc writer/flatten_bindings_test.cc writer/float_to_string_test.cc diff --git a/src/tint/utils/hash.h b/src/tint/utils/hash.h index 3deb14bcc4..d1fac116b6 100644 --- a/src/tint/utils/hash.h +++ b/src/tint/utils/hash.h @@ -22,6 +22,8 @@ #include #include +#include "src/tint/utils/vector.h" + namespace tint::utils { namespace detail { @@ -66,6 +68,15 @@ void HashCombine(size_t* hash, const std::vector& vector) { } } +/// HashCombine "hashes" together an existing hash and hashable values. +template +void HashCombine(size_t* hash, const utils::Vector& list) { + HashCombine(hash, list.Length()); + for (auto& el : list) { + HashCombine(hash, el); + } +} + /// HashCombine "hashes" together an existing hash and hashable values. template void HashCombine(size_t* hash, const std::tuple& tuple) { diff --git a/src/tint/utils/hash_test.cc b/src/tint/utils/hash_test.cc index cb74df95c7..9e60a6627d 100644 --- a/src/tint/utils/hash_test.cc +++ b/src/tint/utils/hash_test.cc @@ -19,6 +19,7 @@ #include #include "gtest/gtest.h" +#include "src/tint/utils/vector.h" namespace tint::utils { namespace { @@ -35,13 +36,22 @@ TEST(HashTests, Basic) { EXPECT_NE(Hash(std::string("hello")), Hash(std::string("world"))); } -TEST(HashTests, Vector) { +TEST(HashTests, StdVector) { EXPECT_EQ(Hash(std::vector({})), Hash(std::vector({}))); EXPECT_EQ(Hash(std::vector({1, 2, 3})), Hash(std::vector({1, 2, 3}))); EXPECT_NE(Hash(std::vector({1, 2, 3})), Hash(std::vector({1, 2, 4}))); EXPECT_NE(Hash(std::vector({1, 2, 3})), Hash(std::vector({1, 2, 3, 4}))); } +TEST(HashTests, TintVector) { + EXPECT_EQ(Hash(Vector({})), Hash(Vector({}))); + EXPECT_EQ(Hash(Vector({1, 2, 3})), Hash(Vector({1, 2, 3}))); + EXPECT_NE(Hash(Vector({1, 2, 3})), Hash(Vector({1, 2, 4}))); + EXPECT_NE(Hash(Vector({1, 2, 3})), Hash(Vector({1, 2, 3, 4}))); + EXPECT_EQ(Hash(Vector({1, 2, 3})), Hash(Vector({1, 2, 3}))); + EXPECT_EQ(Hash(Vector({1, 2, 3})), Hash(Vector({1, 2, 3}))); +} + TEST(HashTests, Tuple) { EXPECT_EQ(Hash(std::make_tuple(1)), Hash(std::make_tuple(1))); EXPECT_EQ(Hash(std::make_tuple(1, 2, 3)), Hash(std::make_tuple(1, 2, 3))); diff --git a/src/tint/utils/transform.h b/src/tint/utils/transform.h index ff925303ca..0c912635f7 100644 --- a/src/tint/utils/transform.h +++ b/src/tint/utils/transform.h @@ -21,6 +21,7 @@ #include #include "src/tint/traits.h" +#include "src/tint/utils/vector.h" namespace tint::utils { @@ -52,6 +53,94 @@ auto Transform(const std::vector& in, TRANSFORMER&& transform) return result; } +/// Transform performs an element-wise transformation of a vector. +/// @param in the input vector. +/// @param transform the transformation function with signature: `OUT(IN)` +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(const Vector& in, TRANSFORMER&& transform) + -> Vector { + Vector result(in.Length()); + for (size_t i = 0; i < result.Length(); ++i) { + result[i] = transform(in[i]); + } + return result; +} + +/// Transform performs an element-wise transformation of a vector. +/// @param in the input vector. +/// @param transform the transformation function with signature: `OUT(IN, size_t)` +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(const Vector& in, TRANSFORMER&& transform) + -> Vector { + Vector result(in.Length()); + for (size_t i = 0; i < result.Length(); ++i) { + result[i] = transform(in[i], i); + } + return result; +} + +/// Transform performs an element-wise transformation of a vector reference. +/// @param in the input vector. +/// @param transform the transformation function with signature: `OUT(IN)` +/// @tparam N the small-array size of the returned Vector +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(const VectorRef& in, TRANSFORMER&& transform) + -> Vector { + Vector result(in.Length()); + for (size_t i = 0; i < result.Length(); ++i) { + result[i] = transform(in[i]); + } + return result; +} + +/// Transform performs an element-wise transformation of a vector reference. +/// @param in the input vector. +/// @param transform the transformation function with signature: `OUT(IN, size_t)` +/// @tparam N the small-array size of the returned Vector +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(const VectorRef& in, TRANSFORMER&& transform) + -> Vector { + Vector result(in.Length()); + for (size_t i = 0; i < result.Length(); ++i) { + result[i] = transform(in[i], i); + } + return result; +} + +/// Transform performs an element-wise transformation of a vector reference. +/// @param in the input vector. +/// @param transform the transformation function with signature: `OUT(IN)` +/// @tparam N the small-array size of the returned Vector +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(ConstVectorRef in, TRANSFORMER&& transform) + -> Vector { + Vector result(in.Length()); + for (size_t i = 0; i < result.Length(); ++i) { + result[i] = transform(in[i]); + } + return result; +} + +/// Transform performs an element-wise transformation of a vector reference. +/// @param in the input vector. +/// @param transform the transformation function with signature: `OUT(IN, size_t)` +/// @tparam N the small-array size of the returned Vector +/// @returns a new vector with each element of the source vector transformed by `transform`. +template +auto Transform(ConstVectorRef in, TRANSFORMER&& transform) + -> Vector { + Vector result(in.Length()); + for (size_t i = 0; i < result.Length(); ++i) { + result[i] = transform(in[i], i); + } + return result; +} + /// TransformN performs an element-wise transformation of a vector, transforming and returning at /// most `n` elements. /// @param in the input vector. diff --git a/src/tint/utils/transform_test.cc b/src/tint/utils/transform_test.cc index 89c07568c9..656b827f2b 100644 --- a/src/tint/utils/transform_test.cc +++ b/src/tint/utils/transform_test.cc @@ -26,7 +26,7 @@ namespace tint::utils { namespace { -TEST(TransformTest, Empty) { +TEST(TransformTest, StdVectorEmpty) { const std::vector empty{}; { auto transformed = Transform(empty, [](int) -> int { @@ -46,21 +46,21 @@ TEST(TransformTest, Empty) { } } -TEST(TransformTest, Identity) { +TEST(TransformTest, StdVectorIdentity) { const std::vector input{1, 2, 3, 4}; auto transformed = Transform(input, [](int i) { return i; }); CHECK_ELEMENT_TYPE(transformed, int); EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); } -TEST(TransformTest, IdentityWithIndex) { +TEST(TransformTest, StdVectorIdentityWithIndex) { const std::vector input{1, 2, 3, 4}; auto transformed = Transform(input, [](int i, size_t) { return i; }); CHECK_ELEMENT_TYPE(transformed, int); EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); } -TEST(TransformTest, Index) { +TEST(TransformTest, StdVectorIndex) { const std::vector input{10, 20, 30, 40}; { auto transformed = Transform(input, [](int, size_t idx) { return idx; }); @@ -69,7 +69,7 @@ TEST(TransformTest, Index) { } } -TEST(TransformTest, TransformSameType) { +TEST(TransformTest, TransformStdVectorSameType) { const std::vector input{1, 2, 3, 4}; { auto transformed = Transform(input, [](int i) { return i * 10; }); @@ -78,7 +78,7 @@ TEST(TransformTest, TransformSameType) { } } -TEST(TransformTest, TransformDifferentType) { +TEST(TransformTest, TransformStdVectorDifferentType) { const std::vector input{1, 2, 3, 4}; { auto transformed = Transform(input, [](int i) { return std::to_string(i); }); @@ -87,7 +87,7 @@ TEST(TransformTest, TransformDifferentType) { } } -TEST(TransformNTest, Empty) { +TEST(TransformNTest, StdVectorEmpty) { const std::vector empty{}; { auto transformed = TransformN(empty, 4u, [](int) -> int { @@ -107,7 +107,7 @@ TEST(TransformNTest, Empty) { } } -TEST(TransformNTest, Identity) { +TEST(TransformNTest, StdVectorIdentity) { const std::vector input{1, 2, 3, 4}; { auto transformed = TransformN(input, 0u, [](int) { @@ -129,7 +129,7 @@ TEST(TransformNTest, Identity) { } } -TEST(TransformNTest, IdentityWithIndex) { +TEST(TransformNTest, StdVectorIdentityWithIndex) { const std::vector input{1, 2, 3, 4}; { auto transformed = TransformN(input, 0u, [](int, size_t) { @@ -151,7 +151,7 @@ TEST(TransformNTest, IdentityWithIndex) { } } -TEST(TransformNTest, Index) { +TEST(TransformNTest, StdVectorIndex) { const std::vector input{10, 20, 30, 40}; { auto transformed = TransformN(input, 0u, [](int, size_t) { @@ -173,7 +173,7 @@ TEST(TransformNTest, Index) { } } -TEST(TransformNTest, TransformSameType) { +TEST(TransformNTest, StdVectorTransformSameType) { const std::vector input{1, 2, 3, 4}; { auto transformed = TransformN(input, 0u, [](int, size_t) { @@ -195,7 +195,7 @@ TEST(TransformNTest, TransformSameType) { } } -TEST(TransformNTest, TransformDifferentType) { +TEST(TransformNTest, StdVectorTransformDifferentType) { const std::vector input{1, 2, 3, 4}; { auto transformed = TransformN(input, 0u, [](int) { @@ -217,5 +217,200 @@ TEST(TransformNTest, TransformDifferentType) { } } +TEST(TransformTest, TintVectorEmpty) { + const Vector empty{}; + { + auto transformed = Transform(empty, [](int) -> int { + [] { FAIL() << "Callback should not be called for empty vector"; }(); + return 0; + }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_EQ(transformed.Length(), 0u); + } + { + auto transformed = Transform(empty, [](int, size_t) -> int { + [] { FAIL() << "Callback should not be called for empty vector"; }(); + return 0; + }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_EQ(transformed.Length(), 0u); + } +} + +TEST(TransformTest, TintVectorIdentity) { + const Vector input{1, 2, 3, 4}; + auto transformed = Transform(input, [](int i) { return i; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); +} + +TEST(TransformTest, TintVectorIdentityWithIndex) { + const Vector input{1, 2, 3, 4}; + auto transformed = Transform(input, [](int i, size_t) { return i; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); +} + +TEST(TransformTest, TintVectorIndex) { + const Vector input{10, 20, 30, 40}; + { + auto transformed = Transform(input, [](int, size_t idx) { return idx; }); + CHECK_ELEMENT_TYPE(transformed, size_t); + EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u)); + } +} + +TEST(TransformTest, TransformTintVectorSameType) { + const Vector input{1, 2, 3, 4}; + { + auto transformed = Transform(input, [](int i) { return i * 10; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40)); + } +} + +TEST(TransformTest, TransformTintVectorDifferentType) { + const Vector input{1, 2, 3, 4}; + { + auto transformed = Transform(input, [](int i) { return std::to_string(i); }); + CHECK_ELEMENT_TYPE(transformed, std::string); + EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4")); + } +} + +TEST(TransformTest, VectorRefEmpty) { + Vector empty{}; + VectorRef ref(empty); + { + auto transformed = Transform<4>(ref, [](int) -> int { + [] { FAIL() << "Callback should not be called for empty vector"; }(); + return 0; + }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_EQ(transformed.Length(), 0u); + } + { + auto transformed = Transform<4>(ref, [](int, size_t) -> int { + [] { FAIL() << "Callback should not be called for empty vector"; }(); + return 0; + }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_EQ(transformed.Length(), 0u); + } +} + +TEST(TransformTest, VectorRefIdentity) { + Vector input{1, 2, 3, 4}; + VectorRef ref(input); + auto transformed = Transform<8>(ref, [](int i) { return i; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); +} + +TEST(TransformTest, VectorRefIdentityWithIndex) { + Vector input{1, 2, 3, 4}; + VectorRef ref(input); + auto transformed = Transform<2>(ref, [](int i, size_t) { return i; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); +} + +TEST(TransformTest, VectorRefIndex) { + Vector input{10, 20, 30, 40}; + VectorRef ref(input); + { + auto transformed = Transform<4>(ref, [](int, size_t idx) { return idx; }); + CHECK_ELEMENT_TYPE(transformed, size_t); + EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u)); + } +} + +TEST(TransformTest, TransformVectorRefSameType) { + Vector input{1, 2, 3, 4}; + VectorRef ref(input); + { + auto transformed = Transform<4>(ref, [](int i) { return i * 10; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40)); + } +} + +TEST(TransformTest, TransformVectorRefDifferentType) { + Vector input{1, 2, 3, 4}; + VectorRef ref(input); + { + auto transformed = Transform<4>(ref, [](int i) { return std::to_string(i); }); + CHECK_ELEMENT_TYPE(transformed, std::string); + EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4")); + } +} + +TEST(TransformTest, ConstVectorRefEmpty) { + const Vector empty{}; + ConstVectorRef ref(empty); + { + auto transformed = Transform<4>(ref, [](int) -> int { + [] { FAIL() << "Callback should not be called for empty vector"; }(); + return 0; + }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_EQ(transformed.Length(), 0u); + } + { + auto transformed = Transform<4>(ref, [](int, size_t) -> int { + [] { FAIL() << "Callback should not be called for empty vector"; }(); + return 0; + }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_EQ(transformed.Length(), 0u); + } +} + +TEST(TransformTest, ConstVectorRefIdentity) { + const Vector input{1, 2, 3, 4}; + ConstVectorRef ref(input); + auto transformed = Transform<8>(ref, [](int i) { return i; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); +} + +TEST(TransformTest, ConstVectorRefIdentityWithIndex) { + const Vector input{1, 2, 3, 4}; + ConstVectorRef ref(input); + auto transformed = Transform<2>(ref, [](int i, size_t) { return i; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(1, 2, 3, 4)); +} + +TEST(TransformTest, ConstVectorRefIndex) { + const Vector input{10, 20, 30, 40}; + ConstVectorRef ref(input); + { + auto transformed = Transform<4>(ref, [](int, size_t idx) { return idx; }); + CHECK_ELEMENT_TYPE(transformed, size_t); + EXPECT_THAT(transformed, testing::ElementsAre(0u, 1u, 2u, 3u)); + } +} + +TEST(TransformTest, TransformConstVectorRefSameType) { + const Vector input{1, 2, 3, 4}; + ConstVectorRef ref(input); + { + auto transformed = Transform<4>(ref, [](int i) { return i * 10; }); + CHECK_ELEMENT_TYPE(transformed, int); + EXPECT_THAT(transformed, testing::ElementsAre(10, 20, 30, 40)); + } +} + +TEST(TransformTest, TransformConstVectorRefDifferentType) { + const Vector input{1, 2, 3, 4}; + ConstVectorRef ref(input); + { + auto transformed = Transform<4>(ref, [](int i) { return std::to_string(i); }); + CHECK_ELEMENT_TYPE(transformed, std::string); + EXPECT_THAT(transformed, testing::ElementsAre("1", "2", "3", "4")); + } +} + } // namespace } // namespace tint::utils diff --git a/src/tint/utils/vector.h b/src/tint/utils/vector.h new file mode 100644 index 0000000000..daac42cff2 --- /dev/null +++ b/src/tint/utils/vector.h @@ -0,0 +1,695 @@ +// Copyright 2022 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_TINT_UTILS_VECTOR_H_ +#define SRC_TINT_UTILS_VECTOR_H_ + +#include +#include +#include +#include +#include +#include + +#include "src/tint/utils/bitcast.h" + +namespace tint::utils { + +/// Forward declarations +template +class VectorRef; +template +class ConstVectorRef; + +} // namespace tint::utils + +namespace tint::utils { + +namespace detail { + +/// A slice represents a contigious array of elements of type T. +template +struct Slice { + /// The pointer to the first element in the slice + T* data = nullptr; + + /// The total number of elements in the slice + size_t len = 0; + + /// The total capacity of the backing store for the slice + size_t cap = 0; + + /// Index operator + /// @param i the element index. Must be less than `len`. + /// @returns a reference to the i'th element. + T& operator[](size_t i) { return data[i]; } + + /// Index operator + /// @param i the element index. Must be less than `len`. + /// @returns a reference to the i'th element. + const T& operator[](size_t i) const { return data[i]; } + + /// @returns a reference to the first element in the vector + T& Front() { return data[0]; } + + /// @returns a reference to the first element in the vector + const T& Front() const { return data[0]; } + + /// @returns a reference to the last element in the vector + T& Back() { return data[len - 1]; } + + /// @returns a reference to the last element in the vector + const T& Back() const { return data[len - 1]; } + + /// @returns a pointer to the first element in the vector + T* begin() { return data; } + + /// @returns a pointer to the first element in the vector + const T* begin() const { return data; } + + /// @returns a pointer to one past the last element in the vector + T* end() { return data + len; } + + /// @returns a pointer to one past the last element in the vector + const T* end() const { return data + len; } + + /// @returns a reverse iterator starting with the last element in the vector + auto rbegin() { return std::reverse_iterator(end()); } + + /// @returns a reverse iterator starting with the last element in the vector + auto rbegin() const { return std::reverse_iterator(end()); } + + /// @returns the end for a reverse iterator + auto rend() { return std::reverse_iterator(begin()); } + + /// @returns the end for a reverse iterator + auto rend() const { return std::reverse_iterator(begin()); } +}; + +} // namespace detail + +/// Vector is a small-object-optimized, dynamically-sized vector of contigious elements of type T. +/// +/// Vector will fit `N` elements internally before spilling to heap allocations. If `N` is greater +/// than zero, the internal elements are stored in a 'small array' held internally by the Vector. +/// +/// Vectors can be copied or moved. +/// +/// Copying a vector will either copy to the 'small array' if the number of elements is equal to or +/// less than N, otherwise elements will be copied into a new heap allocation. +/// +/// Moving a vector will reassign ownership of the heap-allocation memory, if the source vector +/// holds its elements in a heap allocation, otherwise a copy will be made as described above. +/// +/// Vector is optimized for CPU performance over memory efficiency. For example: +/// * Moving a vector that stores its elements in a heap allocation to another vector will simply +/// assign the heap allocation, even if the target vector can hold the elements in its 'small +/// array'. This reduces memory copying, but may incur additional memory usage. +/// * Resizing, or popping elements from a vector that has spilled to a heap allocation does not +/// revert back to using the 'small array'. Again, this is to reduce memory copying. +template +class Vector { + public: + /// Type of `T`. + using value_type = T; + + /// Constructor + Vector() = default; + + /// Constructor + /// @param length the initial length of the vector. Elements will be zero-initialized. + explicit Vector(size_t length) { + Reserve(length); + for (size_t i = 0; i < length; i++) { + new (&impl_.slice.data[i]) T{}; + } + impl_.slice.len = length; + } + + /// Constructor + /// @param length the initial length of the vector + /// @param value the value to copy into each element of the vector + Vector(size_t length, const T& value) { + Reserve(length); + for (size_t i = 0; i < length; i++) { + new (&impl_.slice.data[i]) T{value}; + } + impl_.slice.len = length; + } + + /// Constructor + /// @param elements the elements to place into the vector + explicit Vector(std::initializer_list elements) { + Reserve(elements.size()); + for (auto& el : elements) { + new (&impl_.slice.data[impl_.slice.len++]) T{el}; + } + } + + /// Copy constructor + /// @param other the vector to copy + Vector(const Vector& other) { Copy(other.impl_.slice); } + + /// Move constructor + /// @param other the vector to move + Vector(Vector&& other) { MoveOrCopy(VectorRef(std::move(other))); } + + /// Copy constructor (differing N length) + /// @param other the vector to copy + template + Vector(const Vector& other) { + Copy(other.impl_.slice); + } + + /// Move constructor (differing N length) + /// @param other the vector to move + template + Vector(Vector&& other) { + MoveOrCopy(VectorRef(std::move(other))); + } + + /// Move constructor from a mutable vector reference + /// @param other the vector reference to move + Vector(VectorRef&& other) { // NOLINT(runtime/explicit) + MoveOrCopy(std::move(other)); + } + + /// Copy constructor from an immutable vector reference + /// @param other the vector reference to copy + Vector(const ConstVectorRef& other) { // NOLINT(runtime/explicit) + Copy(other.slice_); + } + + /// Destructor + ~Vector() { ClearAndFree(); } + + /// Assignment operator + /// @param other the vector to copy + /// @returns this vector so calls can be chained + Vector& operator=(const Vector& other) { + if (&other != this) { + Copy(other.impl_.slice); + } + return *this; + } + + /// Move operator + /// @param other the vector to move + /// @returns this vector so calls can be chained + Vector& operator=(Vector&& other) { + if (&other != this) { + MoveOrCopy(VectorRef(std::move(other))); + } + return *this; + } + + /// Assignment operator (differing N length) + /// @param other the vector to copy + /// @returns this vector so calls can be chained + template + Vector& operator=(const Vector& other) { + Copy(other.impl_.slice); + return *this; + } + + /// Move operator (differing N length) + /// @param other the vector to copy + /// @returns this vector so calls can be chained + template + Vector& operator=(Vector&& other) { + MoveOrCopy(VectorRef(std::move(other))); + return *this; + } + + /// Index operator + /// @param i the element index. Must be less than `len`. + /// @returns a reference to the i'th element. + T& operator[](size_t i) { return impl_.slice[i]; } + + /// Index operator + /// @param i the element index. Must be less than `len`. + /// @returns a reference to the i'th element. + const T& operator[](size_t i) const { return impl_.slice[i]; } + + /// @return the number of elements in the vector + size_t Length() const { return impl_.slice.len; } + + /// @return the number of elements that the vector could hold before a heap allocation needs to + /// be made + size_t Capacity() const { return impl_.slice.cap; } + + /// Reserves memory to hold at least `new_cap` elements + /// @param new_cap the new vector capacity + void Reserve(size_t new_cap) { + if (new_cap > impl_.slice.cap) { + auto* old_data = impl_.slice.data; + impl_.Allocate(new_cap); + for (size_t i = 0; i < impl_.slice.len; i++) { + new (&impl_.slice.data[i]) T(std::move(old_data[i])); + old_data[i].~T(); + } + impl_.Free(old_data); + } + } + + /// Resizes the vector to the given length, expanding capacity if necessary. + /// New elements are zero-initialized + /// @param new_len the new vector length + void Resize(size_t new_len) { + Reserve(new_len); + for (size_t i = impl_.slice.len; i > new_len; i--) { // Shrink + impl_.slice.data[i - 1].~T(); + } + for (size_t i = impl_.slice.len; i < new_len; i++) { // Grow + new (&impl_.slice.data[i]) T{}; + } + impl_.slice.len = new_len; + } + + /// Copies all the elements from `other` to this vector, replacing the content of this vector. + /// @param other the + template + void Copy(const Vector& other) { + Copy(other.impl_.slice); + } + + /// Clears all elements from the vector, keeping the capacity the same. + void Clear() { + for (size_t i = 0; i < impl_.slice.len; i++) { + impl_.slice.data[i].~T(); + } + impl_.slice.len = 0; + } + + /// Appends a new element to the vector. + /// @param el the element to copy to the vector. + void Push(const T& el) { + if (impl_.slice.len >= impl_.slice.cap) { + Grow(); + } + new (&impl_.slice.data[impl_.slice.len++]) T(el); + } + + /// Appends a new element to the vector. + /// @param el the element to move to the vector. + void Push(T&& el) { + if (impl_.slice.len >= impl_.slice.cap) { + Grow(); + } + new (&impl_.slice.data[impl_.slice.len++]) T(std::move(el)); + } + + /// Appends a new element to the vector. + /// @param args the arguments to pass to the element constructor. + template + void Emplace(ARGS&&... args) { + if (impl_.slice.len >= impl_.slice.cap) { + Grow(); + } + new (&impl_.slice.data[impl_.slice.len++]) T(std::forward(args)...); + } + + /// Removes and returns the last element from the vector. + /// @returns the popped element + T Pop() { return std::move(impl_.slice.data[--impl_.slice.len]); } + + /// @returns true if the vector is empty. + bool IsEmpty() const { return impl_.slice.len == 0; } + + /// @returns a reference to the first element in the vector + T& Front() { return impl_.slice.Front(); } + + /// @returns a reference to the first element in the vector + const T& Front() const { return impl_.slice.Front(); } + + /// @returns a reference to the last element in the vector + T& Back() { return impl_.slice.Back(); } + + /// @returns a reference to the last element in the vector + const T& Back() const { return impl_.slice.Back(); } + + /// @returns a pointer to the first element in the vector + T* begin() { return impl_.slice.begin(); } + + /// @returns a pointer to the first element in the vector + const T* begin() const { return impl_.slice.begin(); } + + /// @returns a pointer to one past the last element in the vector + T* end() { return impl_.slice.end(); } + + /// @returns a pointer to one past the last element in the vector + const T* end() const { return impl_.slice.end(); } + + /// @returns a reverse iterator starting with the last element in the vector + auto rbegin() { return impl_.slice.rbegin(); } + + /// @returns a reverse iterator starting with the last element in the vector + auto rbegin() const { return impl_.slice.rbegin(); } + + /// @returns the end for a reverse iterator + auto rend() { return impl_.slice.rend(); } + + /// @returns the end for a reverse iterator + auto rend() const { return impl_.slice.rend(); } + + private: + /// Friend class (differing specializations of this class) + template + friend class Vector; + + /// Friend class + template + friend class VectorRef; + + /// Friend class + template + friend class ConstVectorRef; + + /// The slice type used by this vector + using Slice = detail::Slice; + + /// Expands the capacity of the vector + void Grow() { Reserve(impl_.slice.cap * 2); } + + /// Moves 'other' to this vector, if possible, otherwise performs a copy. + void MoveOrCopy(VectorRef&& other) { + if (other.can_move_) { + ClearAndFree(); + impl_.slice = other.slice_; + other.slice_ = {}; + } else { + Copy(other.slice_); + } + } + + /// Copies all the elements from `other` to this vector, replacing the content of this vector. + /// @param other the + void Copy(const Slice& other) { + if (impl_.slice.cap < other.len) { + ClearAndFree(); + impl_.Allocate(other.len); + } else { + Clear(); + } + + impl_.slice.len = other.len; + for (size_t i = 0; i < impl_.slice.len; i++) { + new (&impl_.slice.data[i]) T{other.data[i]}; + } + } + + /// Clears the vector, then frees the slice data. + void ClearAndFree() { + Clear(); + impl_.Free(impl_.slice.data); + } + + /// True if this vector uses a small array for small object optimization. + constexpr static bool HasSmallArray = N > 0; + + /// A structure that has the same size and alignment as T. + /// Replacement for std::aligned_storage as this is broken on earlier versions of MSVC. + struct alignas(alignof(T)) TStorage { + /// @returns the storage reinterpreted as a T* + T* Get() { return Bitcast(&data[0]); } + /// @returns the storage reinterpreted as a T* + const T* Get() const { return Bitcast(&data[0]); } + /// Byte array of length sizeof(T) + uint8_t data[sizeof(T)]; + }; + + /// The internal structure for the vector with a small array. + struct ImplWithSmallArray { + std::array small_arr; + Slice slice = {small_arr[0].Get(), 0, N}; + + /// Allocates a new vector of `T` either from #small_arr, or from the heap, then assigns the + /// pointer it to #slice.data, and updates #slice.cap. + void Allocate(size_t new_cap) { + if (new_cap < N) { + slice.data = small_arr[0].Get(); + slice.cap = N; + } else { + slice.data = Bitcast(new TStorage[new_cap]); + slice.cap = new_cap; + } + } + + /// Frees `data`, if not nullptr and isn't a pointer to #small_arr + void Free(T* data) const { + if (data && data != small_arr[0].Get()) { + delete[] Bitcast(data); + } + } + + /// Indicates whether the slice structure can be std::move()d. + /// @returns true if #slice.data does not point to #small_arr + bool CanMove() const { return slice.data != small_arr[0].Get(); } + }; + + /// The internal structure for the vector without a small array. + struct ImplWithoutSmallArray { + Slice slice = {nullptr, 0, 0}; + + /// Allocates a new vector of `T` and assigns it to #slice.data, and updates #slice.cap. + void Allocate(size_t new_cap) { + slice.data = Bitcast(new TStorage[new_cap]); + slice.cap = new_cap; + } + + /// Frees `data`, if not nullptr. + void Free(T* data) const { + if (data) { + delete[] Bitcast(data); + } + } + + /// Indicates whether the slice structure can be std::move()d. + /// @returns true + bool CanMove() const { return true; } + }; + + /// Either a ImplWithSmallArray or ImplWithoutSmallArray based on N. + std::conditional_t impl_; +}; + +/// VectorRef is a weak reference to a Vector, used to pass vectors as parameters, avoiding copies +/// between the caller and the callee. VectorRef can accept a Vector of any 'N' value, decoupling +/// the caller's vector internal size from the callee's vector size. +/// +/// A VectorRef tracks the usage of moves either side of the call. If at the call site, a Vector +/// argument is moved to a VectorRef parameter, and within the callee, the VectorRef parameter is +/// moved to a Vector, then the Vector heap allocation will be moved. For example: +/// +/// ``` +/// void func_a() { +/// Vector vec; +/// // logic to populate 'vec'. +/// func_b(std::move(vec)); // Constructs a VectorRef tracking the move here. +/// } +/// +/// void func_b(VectorRef vec_ref) { +/// // A move was made when calling func_b, so the vector can be moved instead of copied. +/// Vector vec(std::move(vec_ref)); +/// } +/// ``` +template +class VectorRef { + /// The slice type used by this vector reference + using Slice = detail::Slice; + + public: + /// Constructor from a Vector. + /// @param vector the vector reference + template + VectorRef(Vector& vector) // NOLINT(runtime/explicit) + : slice_(vector.impl_.slice), can_move_(false) {} + + /// Constructor from a std::move()'d Vector + /// @param vector the vector being moved + template + VectorRef(Vector&& vector) // NOLINT(runtime/explicit) + : slice_(vector.impl_.slice), can_move_(vector.impl_.CanMove()) {} + + /// Copy constructor + /// @param other the vector reference + VectorRef(const VectorRef& other) : slice_(other.slice_), can_move_(false) {} + + /// Move constructor + /// @param other the vector reference + VectorRef(VectorRef&& other) = default; + + /// Index operator + /// @param i the element index. Must be less than `len`. + /// @returns a reference to the i'th element. + T& operator[](size_t i) { return slice_[i]; } + + /// Index operator + /// @param i the element index. Must be less than `len`. + /// @returns a reference to the i'th element. + const T& operator[](size_t i) const { return slice_[i]; } + + /// @return the number of elements in the vector + size_t Length() const { return slice_.len; } + + /// @return the number of elements that the vector could hold before a heap allocation needs to + /// be made + size_t Capacity() const { return slice_.cap; } + + /// @returns true if the vector is empty. + bool IsEmpty() const { return slice_.len == 0; } + + /// @returns a reference to the first element in the vector + T& Front() { return slice_.Front(); } + + /// @returns a reference to the first element in the vector + const T& Front() const { return slice_.Front(); } + + /// @returns a reference to the last element in the vector + T& Back() { return slice_.Back(); } + + /// @returns a reference to the last element in the vector + const T& Back() const { return slice_.Back(); } + + /// @returns a pointer to the first element in the vector + T* begin() { return slice_.begin(); } + + /// @returns a pointer to the first element in the vector + const T* begin() const { return slice_.begin(); } + + /// @returns a pointer to one past the last element in the vector + T* end() { return slice_.end(); } + + /// @returns a pointer to one past the last element in the vector + const T* end() const { return slice_.end(); } + + /// @returns a reverse iterator starting with the last element in the vector + auto rbegin() { return slice_.rbegin(); } + + /// @returns a reverse iterator starting with the last element in the vector + auto rbegin() const { return slice_.rbegin(); } + + /// @returns the end for a reverse iterator + auto rend() { return slice_.rend(); } + + /// @returns the end for a reverse iterator + auto rend() const { return slice_.rend(); } + + private: + /// Friend classes + template + friend class Vector; + + /// The slice of the vector being referenced. + Slice& slice_; + /// Whether the slice data is passed by r-value reference, and can be moved. + bool can_move_ = false; +}; + +/// ConstVectorRef is a weak, immutable reference to a Vector, used to pass vectors as parameters, +/// avoiding copies between the caller and the callee. VectorRef can accept a Vector of any 'N' +/// value, decoupling the caller's vector internal size from the callee's vector size. +template +class ConstVectorRef { + /// The slice type used by this vector reference + using Slice = detail::Slice; + + public: + /// Constructor from a Vector. + /// @param vector the vector reference + template + ConstVectorRef(const Vector& vector) // NOLINT(runtime/explicit) + : slice_(vector.impl_.slice) {} + + /// Copy constructor + /// @param other the vector reference + ConstVectorRef(const ConstVectorRef& other) = default; + + /// Index operator + /// @param i the element index. Must be less than `len`. + /// @returns a reference to the i'th element. + const T& operator[](size_t i) const { return slice_[i]; } + + /// @return the number of elements in the vector + size_t Length() const { return slice_.len; } + + /// @return the number of elements that the vector could hold before a heap allocation needs to + /// be made + size_t Capacity() const { return slice_.cap; } + + /// @returns true if the vector is empty. + bool IsEmpty() const { return slice_.len == 0; } + + /// @returns a reference to the first element in the vector + const T& Front() const { return slice_.Front(); } + + /// @returns a reference to the last element in the vector + const T& Back() const { return slice_.Back(); } + + /// @returns a pointer to the first element in the vector + const T* begin() const { return slice_.begin(); } + + /// @returns a pointer to one past the last element in the vector + const T* end() const { return slice_.end(); } + + /// @returns a reverse iterator starting with the last element in the vector + auto rbegin() const { return slice_.rbegin(); } + + /// @returns the end for a reverse iterator + auto rend() const { return slice_.rend(); } + + private: + /// Friend classes + template + friend class Vector; + + /// The slice of the vector being referenced. + const Slice& slice_; +}; + +/// Helper for converting a Vector to a std::vector. +/// @note This helper exists to help code migration. Avoid if possible. +template +std::vector ToStdVector(const Vector& vector) { + std::vector out; + out.reserve(vector.Length()); + for (auto& el : vector) { + out.emplace_back(el); + } + return out; +} + +/// Helper for converting a std::vector to a Vector. +/// @note This helper exists to help code migration. Avoid if possible. +template +Vector ToVector(const std::vector& vector) { + Vector out; + out.Reserve(vector.size()); + for (auto& el : vector) { + out.Push(el); + } + return out; +} + +/// Helper for constructing a Vector from a set of elements. +/// The returned Vector's small-array size (`N`) is equal to the number of provided elements. +/// @param elements the elements used to construct the vector. +template +auto MakeVector(Ts&&... elements) { + return Vector({std::forward(elements)...}); +} + +} // namespace tint::utils + +#endif // SRC_TINT_UTILS_VECTOR_H_ diff --git a/src/tint/utils/vector_test.cc b/src/tint/utils/vector_test.cc new file mode 100644 index 0000000000..8c2c0cf2f1 --- /dev/null +++ b/src/tint/utils/vector_test.cc @@ -0,0 +1,1359 @@ +// Copyright 2022 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/tint/utils/vector.h" + +#include +#include + +#include "gtest/gtest.h" + +#include "src/tint/utils/bitcast.h" + +namespace tint::utils { +namespace { + +/// @returns true if the address of el is within the memory of the vector vec. +template +bool IsInternal(Vector& vec, E& el) { + auto ptr = Bitcast(&el); + auto base = Bitcast(&vec); + return ptr >= base && ptr < base + sizeof(vec); +} + +/// @returns true if all elements of the vector `vec` are held within the memory of `vec`. +template +bool AllInternallyHeld(Vector& vec) { + for (auto& el : vec) { + if (!IsInternal(vec, el)) { + return false; + } + } + return true; +} + +/// @returns true if all elements of the vector `vec` are held outside the memory of `vec`. +template +bool AllExternallyHeld(Vector& vec) { + for (auto& el : vec) { + if (IsInternal(vec, el)) { + return false; + } + } + return true; +} + +TEST(TintVectorTest, SmallArray_Empty) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); +} + +TEST(TintVectorTest, Empty_NoSmallArray) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 0u); +} + +TEST(TintVectorTest, SmallArray_ConstructLength_NoSpill) { + Vector vec(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], 0); + EXPECT_EQ(vec[1], 0); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, SmallArray_ConstructLength_WithSpill) { + Vector vec(3); + EXPECT_EQ(vec.Length(), 3u); + EXPECT_EQ(vec.Capacity(), 3u); + EXPECT_EQ(vec[0], 0); + EXPECT_EQ(vec[1], 0); + EXPECT_EQ(vec[2], 0); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, SmallArray_ConstructLengthValue_NoSpill) { + Vector vec(2, "abc"); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "abc"); + EXPECT_EQ(vec[1], "abc"); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, SmallArray_ConstructLengthValue_WithSpill) { + Vector vec(3, "abc"); + EXPECT_EQ(vec.Length(), 3u); + EXPECT_EQ(vec.Capacity(), 3u); + EXPECT_EQ(vec[0], "abc"); + EXPECT_EQ(vec[1], "abc"); + EXPECT_EQ(vec[2], "abc"); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, ConstructLength_NoSmallArray) { + Vector vec(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], 0); + EXPECT_EQ(vec[1], 0); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, ConstructInitializerList_NoSpill) { + Vector vec{"one", "two"}; + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "one"); + EXPECT_EQ(vec[1], "two"); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, ConstructInitializerList_WithSpill) { + Vector vec{"one", "two", "three"}; + EXPECT_EQ(vec.Length(), 3u); + EXPECT_EQ(vec.Capacity(), 3u); + EXPECT_EQ(vec[0], "one"); + EXPECT_EQ(vec[1], "two"); + EXPECT_EQ(vec[2], "three"); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, ConstructInitializerList_NoSmallArray) { + Vector vec{"one", "two"}; + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "one"); + EXPECT_EQ(vec[1], "two"); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, CopyCtor_NoSpill_N2_to_N2) { + Vector vec_a{"hello", "world"}; + Vector vec_b{vec_a}; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyCtor_WithSpill_N2_to_N2) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{vec_a}; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveCtor_NoSpill_N2_to_N2) { + Vector vec_a{"hello", "world"}; + Vector vec_b{std::move(vec_a)}; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveCtor_WithSpill_N2_to_N2) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{std::move(vec_a)}; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyCtor_NoSpill_N2_to_N1) { + Vector vec_a{"hello", "world"}; + Vector vec_b{vec_a}; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyCtor_WithSpill_N2_to_N1) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{vec_a}; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveCtor_NoSpill_N2_to_N1) { + Vector vec_a{"hello", "world"}; + Vector vec_b{std::move(vec_a)}; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveCtor_WithSpill_N2_to_N1) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{std::move(vec_a)}; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyCtor_NoSpill_N2_to_N3) { + Vector vec_a{"hello", "world"}; + Vector vec_b{vec_a}; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyCtor_WithSpill_N2_to_N3) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{vec_a}; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveCtor_NoSpill_N2_to_N3) { + Vector vec_a{"hello", "world"}; + Vector vec_b{std::move(vec_a)}; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveCtor_WithSpill_N2_to_N3) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{std::move(vec_a)}; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N2) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N2) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N2) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_WithSpill_N2_to_N2) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N1) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N1) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N1) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_SpillSpill_N2_to_N1) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N3) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N3) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N3) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_WithSpill_N2_to_N3) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_NoSpill_N2_to_N0) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_WithSpill_N2_to_N0) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = vec_a; + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_NoSpill_N2_to_N0) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, MoveAssign_WithSpill_N2_to_N0) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b = std::move(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, CopyAssign_Self_NoSpill) { + Vector vec{"hello", "world"}; + auto* vec_ptr = &vec; // Used to avoid -Wself-assign-overloaded + vec = *vec_ptr; + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, CopyAssign_Self_WithSpill) { + Vector vec{"hello", "world"}; + auto* vec_ptr = &vec; // Used to avoid -Wself-assign-overloaded + vec = *vec_ptr; + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, MoveAssign_Self_NoSpill) { + Vector vec{"hello", "world"}; + auto* vec_ptr = &vec; // Used to avoid -Wself-move + vec = std::move(*vec_ptr); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, MoveAssign_Self_WithSpill) { + Vector vec{"hello", "world"}; + auto* vec_ptr = &vec; // Used to avoid -Wself-move + vec = std::move(*vec_ptr); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, RepeatMoveAssign_NoSpill) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"Ciao", "mondo"}; + Vector vec_c{"Bonjour", "le", "monde"}; + Vector vec; + vec = std::move(vec_a); + vec = std::move(vec_b); + vec = std::move(vec_c); + EXPECT_EQ(vec.Length(), 3u); + EXPECT_EQ(vec.Capacity(), 3u); + EXPECT_EQ(vec[0], "Bonjour"); + EXPECT_EQ(vec[1], "le"); + EXPECT_EQ(vec[2], "monde"); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, DoubleMoveAssign_WithSpill) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"Ciao", "mondo"}; + Vector vec_c{"bonjour", "le", "monde"}; + Vector vec; + vec = std::move(vec_a); + vec = std::move(vec_b); + vec = std::move(vec_c); + EXPECT_EQ(vec.Length(), 3u); + EXPECT_EQ(vec.Capacity(), 3u); + EXPECT_EQ(vec[0], "bonjour"); + EXPECT_EQ(vec[1], "le"); + EXPECT_EQ(vec[2], "monde"); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, Index) { + Vector vec{"hello", "world"}; + static_assert(!std::is_const_v>); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], "world"); +} + +TEST(TintVectorTest, ConstIndex) { + const Vector vec{"hello", "world"}; + static_assert(std::is_const_v>); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], "world"); +} + +TEST(TintVectorTest, SmallArray_Reserve_NoSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); + vec.Reserve(1); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); + vec.Reserve(2); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); + vec.Push("hello"); + vec.Push("world"); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_TRUE(AllInternallyHeld(vec)); + vec.Reserve(1); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, SmallArray_Reserve_WithSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 1u); + vec.Reserve(1); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 1u); + vec.Reserve(2); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); + vec.Push("hello"); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec.Push("world"); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec.Reserve(1); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, SmallArray_Resize_NoSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); + vec.Resize(1); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], ""); + EXPECT_TRUE(AllInternallyHeld(vec)); + vec[0] = "hello"; + vec.Resize(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], ""); + EXPECT_TRUE(AllInternallyHeld(vec)); + vec[1] = "world"; + vec.Resize(1); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_TRUE(AllInternallyHeld(vec)); + vec.Resize(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], ""); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, SmallArray_Resize_WithSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 1u); + vec.Resize(1); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_EQ(vec.Capacity(), 1u); + EXPECT_EQ(vec[0], ""); + EXPECT_TRUE(AllInternallyHeld(vec)); + vec[0] = "hello"; + vec.Resize(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], ""); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec[1] = "world"; + vec.Resize(1); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec.Resize(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], ""); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, Reserve_NoSmallArray) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 0u); + vec.Reserve(1); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 1u); + vec.Reserve(2); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); + vec.Push("hello"); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec.Push("world"); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec.Reserve(1); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, Resize_NoSmallArray) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 0u); + vec.Resize(1); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_EQ(vec.Capacity(), 1u); + EXPECT_EQ(vec[0], ""); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec[0] = "hello"; + vec.Resize(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], ""); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec[1] = "world"; + vec.Resize(1); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_TRUE(AllExternallyHeld(vec)); + vec.Resize(2); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_EQ(vec.Capacity(), 2u); + EXPECT_EQ(vec[0], "hello"); + EXPECT_EQ(vec[1], ""); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N2_Empty) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N2_NonEmpty) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"hallo", "wereld"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N2_Spill) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"hallo", "wereld", "spill"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N2_Empty) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N2_NonEmpty) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{"hallo", "wereld"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N2_Spill) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{"hallo", "wereld", "morsen"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N1_Empty) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N1_NonEmpty) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"hallo"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N1_Spill) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"hallo", "morsen"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 2u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N1_Empty) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N1_NonEmpty) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{"hallo"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N1_Spill) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{"hallo", "wereld"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N3_Empty) { + Vector vec_a{"hello", "world"}; + Vector vec_b; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N3_NonEmpty) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"hallo", "fijne", "wereld"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_NoSpill_N2_to_N3_Spill) { + Vector vec_a{"hello", "world"}; + Vector vec_b{"hallo", "fijne", "wereld", "morsen"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 2u); + EXPECT_EQ(vec_b.Capacity(), 4u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N3_Empty) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N3_NonEmpty) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{"hallo", "fijne", "wereld"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 3u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Copy_WithSpill_N2_to_N3_Spill) { + Vector vec_a{"hello", "world", "spill"}; + Vector vec_b{"hallo", "fijne", "wereld", "morsen"}; + vec_b.Copy(vec_a); + EXPECT_EQ(vec_b.Length(), 3u); + EXPECT_EQ(vec_b.Capacity(), 4u); + EXPECT_EQ(vec_b[0], "hello"); + EXPECT_EQ(vec_b[1], "world"); + EXPECT_EQ(vec_b[2], "spill"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); +} + +TEST(TintVectorTest, Clear_Empty) { + Vector vec; + vec.Clear(); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); +} + +TEST(TintVectorTest, Clear_NoSpill) { + Vector vec{"hello", "world"}; + vec.Clear(); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 2u); +} + +TEST(TintVectorTest, Clear_WithSpill) { + Vector vec{"hello", "world", "spill"}; + vec.Clear(); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_EQ(vec.Capacity(), 3u); +} + +TEST(TintVectorTest, PushPop_StringNoSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Push("hello"); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Push("world"); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "world"); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "hello"); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, PushPop_StringWithSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Push("hello"); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Push("world"); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_TRUE(AllExternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "world"); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllExternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "hello"); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, PushPop_StringMoveNoSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + std::string hello = "hello"; + vec.Push(std::move(hello)); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + std::string world = "world"; + vec.Push(std::move(world)); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "world"); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "hello"); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, PushPop_StringMoveWithSpill) { + Vector vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Push("hello"); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Push("world"); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_TRUE(AllExternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "world"); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllExternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), "hello"); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, EmplacePop_TupleVarArgNoSpill) { + Vector, 2> vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Emplace(1, 2.0, false); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Emplace(3, 4.0, true); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), std::make_tuple(3, 4.0, true)); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), std::make_tuple(1, 2.0, false)); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); +} + +TEST(TintVectorTest, EmplacePop_TupleVarArgWithSpill) { + Vector, 1> vec; + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Emplace(1, 2.0, false); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllInternallyHeld(vec)); + + vec.Emplace(3, 4.0, true); + EXPECT_EQ(vec.Length(), 2u); + EXPECT_TRUE(AllExternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), std::make_tuple(3, 4.0, true)); + EXPECT_EQ(vec.Length(), 1u); + EXPECT_TRUE(AllExternallyHeld(vec)); + + EXPECT_EQ(vec.Pop(), std::make_tuple(1, 2.0, false)); + EXPECT_EQ(vec.Length(), 0u); + EXPECT_TRUE(AllExternallyHeld(vec)); +} + +TEST(TintVectorTest, IsEmpty) { + Vector vec; + EXPECT_TRUE(vec.IsEmpty()); + vec.Push("one"); + EXPECT_FALSE(vec.IsEmpty()); + vec.Pop(); + EXPECT_TRUE(vec.IsEmpty()); +} + +TEST(TintVectorTest, FrontBack_NoSpill) { + Vector vec{"front", "mid", "back"}; + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + EXPECT_EQ(vec.Front(), "front"); + EXPECT_EQ(vec.Back(), "back"); +} + +TEST(TintVectorTest, FrontBack_WithSpill) { + Vector vec{"front", "mid", "back"}; + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + EXPECT_EQ(vec.Front(), "front"); + EXPECT_EQ(vec.Back(), "back"); +} + +TEST(TintVectorTest, ConstFrontBack_NoSpill) { + const Vector vec{"front", "mid", "back"}; + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec.Front(), "front"); + EXPECT_EQ(vec.Back(), "back"); +} + +TEST(TintVectorTest, ConstFrontBack_WithSpill) { + const Vector vec{"front", "mid", "back"}; + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec.Front(), "front"); + EXPECT_EQ(vec.Back(), "back"); +} + +TEST(TintVectorTest, BeginEnd_NoSpill) { + Vector vec{"front", "mid", "back"}; + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + EXPECT_EQ(vec.begin(), &vec[0]); + EXPECT_EQ(vec.end(), &vec[0] + 3); +} + +TEST(TintVectorTest, BeginEnd_WithSpill) { + Vector vec{"front", "mid", "back"}; + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + EXPECT_EQ(vec.begin(), &vec[0]); + EXPECT_EQ(vec.end(), &vec[0] + 3); +} + +TEST(TintVectorTest, ConstBeginEnd_NoSpill) { + const Vector vec{"front", "mid", "back"}; + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec.begin(), &vec[0]); + EXPECT_EQ(vec.end(), &vec[0] + 3); +} + +TEST(TintVectorTest, ConstBeginEnd_WithSpill) { + const Vector vec{"front", "mid", "back"}; + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec.begin(), &vec[0]); + EXPECT_EQ(vec.end(), &vec[0] + 3); +} + +TEST(TintVectorRefTest, CtorVectorNoMove) { + Vector vec_a{"one", "two"}; + VectorRef vec_ref(vec_a); // No move + Vector vec_b(std::move(vec_ref)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copy, no move +} + +TEST(TintVectorRefTest, CtorVectorMove) { + Vector vec_a{"one", "two"}; + VectorRef vec_ref(std::move(vec_a)); // Move + Vector vec_b(std::move(vec_ref)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); // Move, no copy +} + +TEST(TintVectorRefTest, CopyCtor) { + Vector vec_a{"one", "two"}; + VectorRef vec_ref_a(std::move(vec_a)); + VectorRef vec_ref_b(vec_ref_a); // No move + Vector vec_b(std::move(vec_ref_b)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copy, no move +} + +TEST(TintVectorRefTest, MoveCtor) { + Vector vec_a{"one", "two"}; + VectorRef vec_ref_a(std::move(vec_a)); // Move + VectorRef vec_ref_b(std::move(vec_ref_a)); + Vector vec_b(std::move(vec_ref_b)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllExternallyHeld(vec_b)); // Move, no copy +} + +TEST(TintVectorRefTest, Index) { + Vector vec{"one", "two"}; + VectorRef vec_ref(vec); + static_assert(!std::is_const_v>); + EXPECT_EQ(vec_ref[0], "one"); + EXPECT_EQ(vec_ref[1], "two"); +} + +TEST(TintVectorRefTest, ConstIndex) { + Vector vec{"one", "two"}; + const VectorRef vec_ref(vec); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref[0], "one"); + EXPECT_EQ(vec_ref[1], "two"); +} + +TEST(TintVectorRefTest, Length) { + Vector vec{"one", "two", "three"}; + VectorRef vec_ref(vec); + EXPECT_EQ(vec_ref.Length(), 3u); +} + +TEST(TintVectorRefTest, Capacity) { + Vector vec{"one", "two", "three"}; + VectorRef vec_ref(vec); + EXPECT_EQ(vec_ref.Capacity(), 5u); +} + +TEST(TintVectorRefTest, IsEmpty) { + Vector vec; + VectorRef vec_ref(vec); + EXPECT_TRUE(vec_ref.IsEmpty()); + vec.Push("one"); + EXPECT_FALSE(vec_ref.IsEmpty()); + vec.Pop(); + EXPECT_TRUE(vec_ref.IsEmpty()); +} + +TEST(TintVectorRefTest, FrontBack) { + Vector vec{"front", "mid", "back"}; + VectorRef vec_ref(vec); + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + EXPECT_EQ(vec_ref.Front(), "front"); + EXPECT_EQ(vec_ref.Back(), "back"); +} + +TEST(TintVectorRefTest, ConstFrontBack) { + Vector vec{"front", "mid", "back"}; + const VectorRef vec_ref(vec); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref.Front(), "front"); + EXPECT_EQ(vec_ref.Back(), "back"); +} + +TEST(TintVectorRefTest, BeginEnd) { + Vector vec{"front", "mid", "back"}; + VectorRef vec_ref(vec); + static_assert(!std::is_const_v>); + static_assert(!std::is_const_v>); + EXPECT_EQ(vec_ref.begin(), &vec[0]); + EXPECT_EQ(vec_ref.end(), &vec[0] + 3); +} + +TEST(TintVectorRefTest, ConstBeginEnd) { + Vector vec{"front", "mid", "back"}; + const VectorRef vec_ref(vec); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref.begin(), &vec[0]); + EXPECT_EQ(vec_ref.end(), &vec[0] + 3); +} + +TEST(TintVectorConstRefTest, CtorVectorNoMove) { + Vector vec_a{"one", "two"}; + ConstVectorRef vec_ref(vec_a); // No move + Vector vec_b(std::move(vec_ref)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copy, no move +} + +TEST(TintVectorConstRefTest, CtorVectorMove) { + Vector vec_a{"one", "two"}; + ConstVectorRef vec_ref(std::move(vec_a)); // Move + Vector vec_b(std::move(vec_ref)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copy, no move +} + +TEST(TintVectorConstRefTest, CopyCtor) { + Vector vec_a{"one", "two"}; + ConstVectorRef vec_ref_a(std::move(vec_a)); + ConstVectorRef vec_ref_b(vec_ref_a); // No move + Vector vec_b(std::move(vec_ref_b)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copy, no move +} + +TEST(TintVectorConstRefTest, MoveCtor) { + Vector vec_a{"one", "two"}; + ConstVectorRef vec_ref_a(std::move(vec_a)); // Move + ConstVectorRef vec_ref_b(std::move(vec_ref_a)); + Vector vec_b(std::move(vec_ref_b)); + EXPECT_EQ(vec_b[0], "one"); + EXPECT_EQ(vec_b[1], "two"); + EXPECT_TRUE(AllInternallyHeld(vec_b)); // Copy, no move +} + +TEST(TintVectorConstRefTest, Index) { + Vector vec{"one", "two"}; + ConstVectorRef vec_ref(vec); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref[0], "one"); + EXPECT_EQ(vec_ref[1], "two"); +} + +TEST(TintVectorConstRefTest, ConstIndex) { + Vector vec{"one", "two"}; + const ConstVectorRef vec_ref(vec); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref[0], "one"); + EXPECT_EQ(vec_ref[1], "two"); +} + +TEST(TintVectorConstRefTest, Length) { + Vector vec{"one", "two", "three"}; + ConstVectorRef vec_ref(vec); + EXPECT_EQ(vec_ref.Length(), 3u); +} + +TEST(TintVectorConstRefTest, Capacity) { + Vector vec{"one", "two", "three"}; + ConstVectorRef vec_ref(vec); + EXPECT_EQ(vec_ref.Capacity(), 5u); +} + +TEST(TintVectorConstRefTest, IsEmpty) { + Vector vec; + ConstVectorRef vec_ref(vec); + EXPECT_TRUE(vec_ref.IsEmpty()); + vec.Push("one"); + EXPECT_FALSE(vec_ref.IsEmpty()); + vec.Pop(); + EXPECT_TRUE(vec_ref.IsEmpty()); +} + +TEST(TintVectorConstRefTest, FrontBack) { + Vector vec{"front", "mid", "back"}; + ConstVectorRef vec_ref(vec); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref.Front(), "front"); + EXPECT_EQ(vec_ref.Back(), "back"); +} + +TEST(TintVectorConstRefTest, ConstFrontBack) { + Vector vec{"front", "mid", "back"}; + const ConstVectorRef vec_ref(vec); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref.Front(), "front"); + EXPECT_EQ(vec_ref.Back(), "back"); +} + +TEST(TintVectorConstRefTest, BeginEnd) { + Vector vec{"front", "mid", "back"}; + ConstVectorRef vec_ref(vec); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref.begin(), &vec[0]); + EXPECT_EQ(vec_ref.end(), &vec[0] + 3); +} + +TEST(TintVectorConstRefTest, ConstBeginEnd) { + Vector vec{"front", "mid", "back"}; + const ConstVectorRef vec_ref(vec); + static_assert(std::is_const_v>); + static_assert(std::is_const_v>); + EXPECT_EQ(vec_ref.begin(), &vec[0]); + EXPECT_EQ(vec_ref.end(), &vec[0] + 3); +} + +} // namespace +} // namespace tint::utils