All Notes

Implementing STL's std::vector

Last updated

Oct 25, 2025

Need to do a structured writeup on this. Maybe a blog/not essay tbh. Talk through requirements, test cases, pseudocode, c++ code

Implementation I made… I want to review it properly to see if there’s any way to optimise better/make code cleaner. Tests (GTest) are below this code snippet too

impl

#pragma once

#include <utility>
#include <initializer_list>
#include <algorithm>
template<typename T, typename Allocator = std::allocator<T>>
class Vector {
public:
using iterator = T*;
using const_iterator = const T*;

/**
* @brief Default constructor
*/
Vector() noexcept = default;

/**
* @brief Copy constructor
*/
Vector(const Vector& other) :
size_(other.size_),
capacity_(other.capacity_) {
if (capacity_ > 0) {
data_ = alloc_.allocate(capacity_);
for (size_t i = 0; i < size_; i++) {
alloc_.construct(data_ + i, other.data_[i]);
}
} else {
data_ = nullptr;
}
}

/**
* @brief Move constructor
*/
Vector(Vector&& other) noexcept :
data_(std::exchange(other.data_, nullptr)),
size_(other.size_),
capacity_(other.capacity_) {

other.size_ = 0;
other.capacity_ = 0;
}

/**
* @brief Constructor that reserves count spaces and fills each val with val
*
* @param count The number of spaces to reserve in the vector
* @param val The value to populate each of the reserved spaces with
*/
Vector(size_t count, const T& val) : capacity_(count), size_(count) {
if (capacity_ > 0) {
data_ = alloc_.allocate(capacity_);
for (size_t i = 0; i < size_; i++) {
alloc_.construct(data_ + i, val);
}
} else {
data_ = nullptr;
}
}

/**
* @brief Constructs a new vector from an intiializer list
*
* @param init_list The list from which we obtain values to fill the vector
*/
Vector(std::initializer_list<T> init_list) :
size_(init_list.size()),
capacity_(init_list.size()) {
if (init_list.size() > 0) {
data_ = alloc_.allocate(capacity_);
size_t i = 0;

for(const auto& elem : init_list) {
alloc_.construct(data_ + i++, elem);
}
} else {
data_ = nullptr;
}
}

/**
* @brief Constructs a vector from the range of iterator elements
*
* @param first The beginning of the iterator range.
* @param last The end of the iterator range - the element at the last
* iterator is not included in the range of elements to populate the
* new vector
*/
Vector(iterator first, iterator last) {
size_ = last - first;
capacity_ = size_;
data_ = alloc_.allocate(capacity_);
auto it = first;

for (size_t i = 0; i < size_; i++) {
alloc_.construct(data_ + i, *it);
it++;
}
}

/**
* @brief Destructor - frees up all memory from the vector
*/
~Vector() {
delete_all();
}

/**
* @brief get the capacity of the vector
*/
size_t capacity() const noexcept {
return capacity_;
}

/**
* @brief get the size of the vector
*/
size_t size() const noexcept {
return size_;
}

/**
* @brief check if the vector is empty
*
* @returns true if the vector has no elements, false otherwise
*/
bool empty() const noexcept {
return size() == 0;
}

/**
* @brief reserves some space for the vector to use
*
* No-op if the new capacity is lte current capcity. Allocate
* some new space - this should be equivalent to the new capacity
* specified from the parameter. We then move the data from the
* previously allocated memory. After the data has been moved
* to the newly allocated space we can deallocate the previous
* block of memory.
*
* @param new_cap the new capacity that should be reserved
*/
void reserve(size_t new_cap) {
if (new_cap <= capacity_) return;

T* new_data = alloc_.allocate(new_cap);
for (size_t i = 0; i < size_; i++) {
alloc_.construct(new_data + i, std::move(*(data_ + i)));
alloc_.destroy(data_ + i);
}
alloc_.deallocate(data_, capacity_);

data_ = new_data;
capacity_ = new_cap;
}

/**
* @brief access the data from a specific index
*
* undefined behaviour when accessing element out of bounds
*
* @param pos the index we should be accessing
* @returns the indexed data as a reference val
*/
T& operator[](size_t pos) {
return data_[pos];
}

/**
* @brief access the data from a specific index
*
* @param pos the index we should be accessing
* @returns the indexed data as a const reference
*/
const T& operator[](size_t pos) const {
return data_[pos];
}

/**
* @brief checks whether two vectors are equivalent
*
* we compare against the size and elements in each vector
*
* @param other vector to be compared against
* @returns true if the vectors are equivalent, false otherwise
*/
bool operator==(const Vector& other) const {
bool same_size = size_ == other.size_;

if (!same_size) {
return false;
}

for (size_t i = 0; i < size_; i++) {
if (at(i) != other.at(i)) return false;
}

return true;
}

/**
* @brief checks whether two vectors are not equivalent
*
* @param other vector to be compared against
* @returns true if the vectors are not equivalent, false otherwise
*/
bool operator!=(const Vector& other) const {
return !(*this == other);
}

/**
* @brief copy assignment operator
*
* No-op if the two vectors are equivalent. If the other vector has
* less elements than our current capacity can hold then we just destroy
* all elements from the current space. Then proceed with the copying.
* Otherwise if the other vector has more elements than our current
* capacity can handle we must destroy and deallocate all the existing
* memory. Then allocate new memory and copy elemnts into it from the
* other vector.
*
* @param other vector to copy from
* @returns the new vector, current vector is returned if other
* is the same as the current vector
*/
Vector& operator=(const Vector& other) {
if (this == &other) return *this;

if (other.size_ <= capacity_) {
destroy_all();

for (size_t i = 0; i < other.size_; i++) {
alloc_.construct(data_ + i, other[i]);
}
} else {
T* new_data = alloc_.allocate(other.size_);
for (size_t i = 0; i < other.size_; i++) {
alloc_.construct(new_data + i, other[i]);
}

destroy_all();
alloc_.deallocate(data_, capacity_);

capacity_ = other.size_;
data_ = new_data;
}

size_ = other.size_;

return *this;
}

/**
* @brief move assigment operator
*
* previous memory may be deleted. then perform the move
*
* @param other vector to be compared against
* @returns true if the vectors are equivalent, false otherwise
*/
Vector& operator=(Vector&& other) {
if (this == &other) return *this;

destroy_all();

if (capacity_ > 0) {
alloc_.deallocate(data_, capacity_);
}

data_ = std::exchange(other.data_, nullptr);
size_ = std::exchange(other.size_, 0);
capacity_ = std::exchange(other.capacity_, 0);

return *this;
}

/**
* @brief access element at a specific index
*
* @param pos where to index the memory at
* @returns reference to element at the index
*/
T& at(size_t pos) {
if (pos >= size_) throw std::out_of_range("Vector index out of range");

return data_[pos];
}

/**
* @brief access element at a specific index
*
* @param pos where to index the memory at
* @returns const reference to element at the index
*/
const T& at(size_t pos) const{
if (pos >= size_) throw std::out_of_range("Vector index out of range");

return data_[pos];
}

/**
* @brief get element at the front of the vector
*
* @returns reference to element at the front
*/
T& front() {
return data_[0];
}

/**
* @brief get element at the front of the vector
*
* @returns const reference to element at the front
*/
const T& front() const {
return data_[0];
}

/**
* @brief get element at the back of the vector
*
* @returns reference to element at the back
*/
T& back() {
return data_[size_ - 1];
}

/**
* @brief get element at the back of the vector
*
* @returns const reference to element at the back
*/
const T& back() const {
return data_[size_ - 1];
}

/**
* @brief get underlying pointer to the memory pool
*
* @returns pointer to vector's memory
*/
T* data() {
return data_;
}

/**
* @brief get underlying pointer to the memory pool
*
* @returns const pointer to vector's memory
*/
const T* data() const {
return data_;
}

/**
* @brief pushes element to the back of the vector
*
* @param value value to push into the back of the vector
*/
void push_back(const T& value) {
ensure_capacity(size_ + 1);
alloc_.construct(data_ + size_, value);
++size_;
}

/**
* @brief pushes element to the back of the vector
*
* @param value rvalue to push into the back of the vector
*/
void push_back(T&& value) {
ensure_capacity(size_ + 1);
alloc_.construct(data_ + size_, std::move(value));
++size_;
}

/**
* @brief removes element from back of the vector
*/
void pop_back() {
alloc_.destroy(data_ + size_ - 1);
--size_;
}

/**
* @brief remove all elements from the vector
*/
void clear() {
while (size_ > 0) {
pop_back();
}
}

/**
* @brief Constructs an element in-place at the end of the vector
* @tparam Args Types of arguments to forward to the element constructor
* @param args Arguments to forward to the element constructor
*
* Constructs a new element at the end of the vector using the provided
* arguments. The element is constructed directly in the vector's memory,
* avoiding unnecessary copies or moves.
*/
template<typename... Args>
void emplace_back(Args... args) {
ensure_capacity(size_ + 1);
alloc_.construct(data_ + size_, std::forward<Args>(args)...);
++size_;
}

/**
* @brief get iterator to the beginning of the vector
*
* @returns iterator to the first element
*/
iterator begin() { return data_; }

/**
* @brief get iterator to the end of the vector
*
* @returns iterator to one past the last element
*/
iterator end() { return data_ + (size_ - 1); }

/**
* @brief get const iterator to the beginning of the vector
*
* @returns const iterator to the first element
*/
const_iterator begin() const { return data_; }

/**
* @brief get const iterator to the end of the vector
*
* @returns const iterator to one past the last element
*/
const_iterator end() const { return data_ + (size_ - 1); }

/**
* @brief get const iterator to the beginning of the vector
*
* @returns const iterator to the first element
*/
const_iterator cbegin() const { return data_; }

/**
* @brief get const iterator to the end of the vector
*
* @returns const iterator to one past the last element
*/
const_iterator cend() const { return data_ + (size_ - 1); }

/**
* @brief insert value at a given position
*
* @param pos position to insert the value at
* @param value value to insert
*/
iterator insert(const_iterator pos, const T&value) {
return insert_impl(pos, 1, value);
}

/**
* @brief insert value at a given position
*
* @param pos position to insert the value at
* @param value rvalue to insert
*/
iterator insert(const_iterator pos, T&&value) {
return insert_impl(pos, 1, std::move(value));
}

/**
* @brief repeatedly insert a value at a given position
*
* @param pos position to start inserting the values at
* @param count number of times to insert the value in the vector
* @param value value to insert
*/
iterator insert(const_iterator pos, size_t count, const T&value) {
return insert_impl(pos, count, value);
}

/**
* @brief insert a range of values into the vector at a given position
*
* @param pos position to start inserting the values at
* @param first iterator to the first value in the range that should be inserted
* @param last last value in the range - not to be included in the insertion
*/
iterator insert(const_iterator pos, iterator first, iterator last) {
return insert_range_impl(pos, first, last);
}

/**
* @brief inserts values from an intiializer_list at a given position
*
* @param pos position to start inserting the values at
* @param ilist initializer list
*/
iterator insert(const_iterator pos, std::initializer_list<T> ilist) {
auto first = ilist.begin();
auto range_size = ilist.size();
return insert_range_impl(pos, first, first + range_size);
}

/**
* @brief removes element from a given position
*
* @param pos position to erase the value from
* @returns iterator to the element at pos after erase() has been run
*/
iterator erase(const_iterator pos) {
size_t pos_idx = pos - begin();
alloc_.destroy(data_ + pos_idx);

for(size_t i = pos_idx; i < size_ - 1; i++) {
alloc_.construct(data_ + i, std::move(data_[i + 1]));
alloc_.destroy(data_ + i + 1);
}

--size_;
return begin() + pos_idx;
}

/**
* @brief removes range of elements from vector (first, last]
*
* @param first first element in range to be erased
* @param last last element in range to be erased - not included in removal
* @returns iterator to position where element was originally removed from
*/
iterator erase(iterator first, iterator last) {
size_t pos_idx = first - begin();
size_t count = last - first;

for (size_t i = 0; i < count; i++) {
alloc_.destroy(data_ + pos_idx + i);
}

for(size_t i = pos_idx; i < size_ - 1; i++) {
alloc_.construct(data_ + i, std::move(data_[i + count]));
alloc_.destroy(data_ + i + count);
}

size_ -= count;
return first;
}

/**
* @brief removes range of elements from vector (first, last]
*
* @param first first element in range to be erased
* @param last last element in range to be erased - not included in removal
* @returns iterator to position where element was originally removed from
*/
iterator erase(const_iterator first, const_iterator last) {
size_t pos_idx = first - begin();
size_t count = last - first;

for (size_t i = 0; i < count; i++) {
alloc_.destroy(data_ + pos_idx + i);
}

for(size_t i = pos_idx; i < size_ - 1; i++) {
alloc_.construct(data_ + i, std::move(data_[i + count]));
alloc_.destroy(data_ + i + count);
}

size_ -= count;
return begin() + pos_idx;
}

/**
* @brief resizes the vector to contain count elements
*
* If count is greater than current size, new elements are default-constructed.
* If count is less than current size, excess elements are destroyed.
*
* @param count new size of the vector
*/
void resize(size_t count) {
if (count == size_) return;

if (count > capacity_) {
size_t new_capacity = count;
T* new_data = alloc_.allocate(new_capacity);

for (size_t i = 0; i < new_capacity; i++) {
if (i < size_) {
alloc_.construct(new_data + i, std::move(data_[i]));
alloc_.destroy(data_ + i);
} else {
alloc_.construct(new_data + i);
}
}

alloc_.deallocate(data_, capacity_);
capacity_ = new_capacity;
data_ = new_data;
} else if (count <= capacity_ && count > size_) {
for (size_t i = size_; i < count; i++) {
alloc_.construct(data_ + i);
}
} else {
for (size_t i = count; i < size_; i++) {
alloc_.destroy(data_ + i);
}
}

size_ = count;
}

/**
* @brief resizes the vector to contain count elements
*
* If count is greater than current size, new elements are initialized with value.
* If count is less than current size, excess elements are destroyed.
*
* @param count new size of the vector
* @param value value to initialize new elements with
*/
void resize(size_t count, const T& value) {
if (count == size_) return;

if (count > capacity_) {
size_t new_capacity = count;
T* new_data = alloc_.allocate(new_capacity);

for (size_t i = 0; i < new_capacity; i++) {
if (i < size_) {
alloc_.construct(new_data + i, std::move(data_[i]));
alloc_.destroy(data_ + i);
} else {
alloc_.construct(new_data + i, value);
}
}

alloc_.deallocate(data_, capacity_);
capacity_ = new_capacity;
data_ = new_data;
} else if (count <= capacity_ && count > size_) {
for (size_t i = size_; i < count; i++) {
alloc_.construct(data_ + i, value);
}
} else {
for (size_t i = count; i < size_; i++) {
alloc_.destroy(data_ + i);
}
}

size_ = count;
}

/**
* @brief reduces capacity to match the current size
*
* Requests the vector to reduce its capacity to fit its size.
* This may cause a reallocation, but has no effect on the vector size.
*/
void shrink_to_fit() {
if (capacity_ == size_) return;

size_t new_capacity = size_;
T* new_data = alloc_.allocate(new_capacity);

for (size_t i = 0; i < capacity_; i++) {
if (i < new_capacity) {
alloc_.construct(new_data + i, std::move(data_[i]));
}

alloc_.destroy(data_ + i);
}
alloc_.deallocate(data_, capacity_);

capacity_ = size_;
data_ = new_data;
}

/**
* @brief exchanges the contents of the vector with another vector
*
* @param other vector to swap contents with
*/
void swap(Vector<T>& other) {
std::swap(data_, other.data_);
std::swap(size_, other.size_);
std::swap(capacity_, other.capacity_);
std::swap(alloc_, other.alloc_);
}

private:
/**
* @brief ensures the vector has at least the specified capacity
*
* @param min_capacity minimum capacity required
*/
void ensure_capacity(size_t min_capacity) {
if (min_capacity > capacity_) {

size_t new_capacity = std::max(
min_capacity,
capacity_ > 0 ? capacity_ * 2 : 1);
T* new_data = alloc_.allocate(new_capacity);

for(size_t i = 0; i < size_; i++) {
alloc_.construct(new_data + i, std::move(data_[i]));
alloc_.destroy(data_ + i);
}

if (capacity_) alloc_.deallocate(data_, capacity_);

data_ = new_data;
capacity_ = new_capacity;
}
}

/**
* @brief inserts elements at specified position
*
* @tparam Args types of arguments to forward
* @param pos position to insert at
* @param count number of elements to insert
* @param args arguments to forward to element constructor
* @returns iterator to first inserted element
*/
template<typename... Args>
iterator insert_impl(const_iterator pos, size_t count, Args&&... args) {
size_t pos_idx = pos - begin();
if (count == 0) return begin() + pos_idx;

ensure_capacity(size_ + count);

// shift elements to the right
for (size_t i = size_; i > pos_idx; i--) {
size_t original_element_idx = i - 1;
alloc_.construct(data_ + original_element_idx + count, std::move(data_[original_element_idx]));
alloc_.destroy(data_ + original_element_idx);
}

// insert new elem
for (size_t i = 0; i < count; i++) {
alloc_.construct(data_ + pos_idx + i, std::forward<Args>(args)...);
}

size_ += count;
return begin() + pos_idx;
}

/**
* @brief inserts range of elements at specified position
*
* @param pos position to insert at
* @param first iterator to first element in range
* @param last iterator to one past last element in range
* @returns iterator to first inserted element
*/
iterator insert_range_impl(const_iterator pos, const_iterator first, const_iterator last) {
size_t pos_idx = pos - begin();
size_t count = last - first;
if (count == 0) return begin() + pos_idx;

ensure_capacity(size_ + count);

// shift elements to the right
for (size_t i = size_; i > pos_idx; i--) {
size_t original_element_idx = i - 1;
alloc_.construct(data_ + original_element_idx + count, std::move(data_[original_element_idx]));
alloc_.destroy(data_ + original_element_idx);
}

// insert new elem
auto it = first;
for (size_t i = 0; i < count; i++) {
alloc_.construct(data_ + pos_idx + i, *it);
it++;
}

size_ += count;
return begin() + pos_idx;
}

/**
* @brief destroys all elements without deallocating memory
*/
void destroy_all() {
for(size_t i = 0; i < size_; i++) {
alloc_.destroy(data_ + i);
}
}

/**
* @brief destroys all elements and deallocates memory
*/
void delete_all() {
destroy_all();
if (capacity_) {
alloc_.deallocate(data_, capacity_);
}
data_ = nullptr;
size_ = capacity_ = 0;
}

T* data_{nullptr};
size_t size_{0};
size_t capacity_{0};
Allocator alloc_;
};

tests

#include <gtest/gtest.h>
#include "vector/Vector.h"
#include <string>
#include <iostream>

TEST(VectorTest, EmptyVectorFromDefaultCtor) {
auto vec = Vector<int>();

EXPECT_TRUE(vec.empty());
EXPECT_EQ(vec.size(), 0);
}

TEST(VectorTest, CopyCtorCreatesCopiedVectorWithSeperateMemory) {
Vector<int> vec1 = {1, 2, 3};
auto vec2 = Vector<int>(vec1);

EXPECT_EQ(vec2.size(), vec1.size());
EXPECT_EQ(vec2.capacity(), vec1.capacity());

// TODO: can just compare vec1 == vec2 when operator== implemented
for(auto i = 0; i < vec1.size(); i++) {
EXPECT_EQ(vec1[i], vec2[i]);
}

// both vectors should be allocated in different parts of memory
EXPECT_FALSE(vec1.data() == vec2.data());
}

TEST(VectorTest, MoveCtorTransfersOwnership) {
Vector<int> vec1 = {1, 2, 3};
Vector<int> vec2(std::move(vec1));

EXPECT_EQ(vec1.size(), 0);
EXPECT_EQ(vec1.capacity(), 0);
EXPECT_EQ(vec1.data(), nullptr);
EXPECT_EQ(vec2.size(), 3);
EXPECT_EQ(vec2.capacity(), 3);
EXPECT_EQ(vec2[0], 1);
EXPECT_EQ(vec2[1], 2);
EXPECT_EQ(vec2[2], 3);
}

TEST(VectorTest, CountCtor) {
Vector<int> vec(3, 1);
EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec.capacity(), 3);

for(auto i = 0; i < vec.size(); i++) {
EXPECT_EQ(vec[i], 1);
}
}

TEST(VectorTest, InitListCtor) {
Vector<int> vec({1, 2, 3});
EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec.capacity(), 3);

EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);
EXPECT_EQ(vec[2], 3);
}

TEST(VectorTest, CtorViaIterators) {
Vector<int> vec1({1, 2, 3, 4, 5});
auto first = vec1.begin() + 1; // 2
auto last = vec1.end(); // 5 (but not included in construction)
Vector<int> vec2(first, last);


std::cout << "new vec size : " << vec2.size() << std::endl;
EXPECT_EQ(vec2.size(), 3);
EXPECT_EQ(vec2.capacity(), 3);
EXPECT_EQ(vec2[0], 2);
EXPECT_EQ(vec2[1], 3);
EXPECT_EQ(vec2[2], 4);
}

TEST(VectorTest, AccessVectorElements) {
Vector<int> vec = {1, 2, 3};

EXPECT_EQ(vec.front(), 1);
EXPECT_EQ(vec.back(), 3);

// operator[]
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);
EXPECT_EQ(vec[2], 3);

// .at()
EXPECT_EQ(vec.at(0), 1);
EXPECT_EQ(vec.at(1), 2);
EXPECT_EQ(vec.at(2), 3);

// does data return the correct ptr
auto ptr = vec.data();
EXPECT_EQ(ptr[0], 1);
EXPECT_EQ(ptr[1], 2);
EXPECT_EQ(ptr[2], 3);
}

TEST(VectorTest, CheckVectorCapacities) {
Vector<int> vec = {1, 2, 3};

EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec.capacity(), 3);
EXPECT_FALSE(vec.empty());
EXPECT_TRUE(Vector<int>().empty());
}

TEST(VectorTest, PushBackCorrectlyAppends) {
Vector<int> vec = {1, 2, 3};
int* old_data = vec.data();
EXPECT_FALSE(old_data == nullptr);

vec.push_back(10);
EXPECT_EQ(vec.size(), 4);
EXPECT_EQ(vec.capacity(), 6);

vec.push_back(11);
EXPECT_EQ(vec.size(), 5);
EXPECT_EQ(vec.capacity(), 6);

vec.push_back(12);
EXPECT_EQ(vec.size(), 6);
EXPECT_EQ(vec.capacity(), 6);

vec.push_back(20);
EXPECT_EQ(vec.size(), 7);
EXPECT_EQ(vec.capacity(), 12);

// we're using a new pool of memory now compared to before
EXPECT_FALSE(old_data == vec.data());
}

TEST(VectorTest, PushBackOnEmptyVector) {
Vector<int> vec;
EXPECT_EQ(vec.size(), 0);
EXPECT_EQ(vec.capacity(), 0);

vec.push_back(1);
EXPECT_EQ(vec.size(), 1);
EXPECT_EQ(vec.capacity(), 1);

vec.push_back(2);
EXPECT_EQ(vec.size(), 2);
EXPECT_EQ(vec.capacity(), 2);

vec.push_back(3);
EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec.capacity(), 4);
}

TEST(VectorTest, PopBack) {
Vector<int> vec = {1, 2, 3};

vec.pop_back();
EXPECT_EQ(vec.size(), 2);
EXPECT_EQ(vec.capacity(), 3);
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);

vec.pop_back();
EXPECT_EQ(vec.size(), 1);
EXPECT_EQ(vec.capacity(), 3);
EXPECT_EQ(vec[0], 1);

vec.pop_back();
EXPECT_EQ(vec.size(), 0);
EXPECT_EQ(vec.capacity(), 3);
EXPECT_TRUE(vec.empty());
}

TEST(VectorTest, ClearVector) {
Vector<int> vec = {1, 2, 3};

vec.clear();
EXPECT_EQ(vec.size(), 0);
EXPECT_EQ(vec.capacity(), 3);
}

TEST(VectorTest, EmplaceBack) {
struct Person {
std::string name_;
int age_;

Person(std::string name, int age) : name_(name), age_(age) {}
};

Person a("Mario", 23);
Person b("Luigi", 21);
Vector<Person> persons = {a, b};
persons.emplace_back("Peach", 22);

EXPECT_EQ(persons.size(), 3);
EXPECT_EQ(persons[0].name_, "Mario");
EXPECT_EQ(persons[1].name_, "Luigi");
EXPECT_EQ(persons[2].name_, "Peach");
}

TEST(VectorTest, InsertValAtIterator) {
Vector<int> vec = {1,2,3,4,5};
auto it = vec.begin();
it++;

vec.insert(it, 10);
EXPECT_EQ(vec.size(), 6);
EXPECT_EQ(vec.capacity(), 10);
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 10);
EXPECT_EQ(vec[2], 2);
EXPECT_EQ(vec[3], 3);
EXPECT_EQ(vec[4], 4);
EXPECT_EQ(vec[5], 5);
}


TEST(VectorTest, InsertRepeatedVals) {
Vector<int> vec = {1, 2, 3};
auto it = vec.begin() + 1;

vec.insert(it, 3, 10);
EXPECT_EQ(vec.size(), 6);
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 10);
EXPECT_EQ(vec[2], 10);
EXPECT_EQ(vec[3], 10);
EXPECT_EQ(vec[4], 2);
EXPECT_EQ(vec[5], 3);
}

TEST(VectorTest, InsertRangeOfValues) {
Vector<int> vec1 = {1, 2, 3};
Vector<int> vec2 = {10, 11, 12, 13, 15};

auto pos = vec1.begin() + 1; // 2
auto first = vec2.begin() + 2; // 12
auto last = vec2.begin() + 4; // 15

vec1.insert(pos, first, last);
EXPECT_EQ(vec1.size(), 5);
EXPECT_EQ(vec1[0], 1);
EXPECT_EQ(vec1[1], 12);
EXPECT_EQ(vec1[2], 13);
EXPECT_EQ(vec1[3], 2);
EXPECT_EQ(vec1[4], 3);
}

TEST(VectorTest, InsertInitList) {
Vector<int> vec1 = {1, 2, 3};

auto pos = vec1.begin() + 1; // 2
auto ilist = {12, 13};

vec1.insert(pos, ilist);
EXPECT_EQ(vec1.size(), 5);
EXPECT_EQ(vec1[0], 1);
EXPECT_EQ(vec1[1], 12);
EXPECT_EQ(vec1[2], 13);
EXPECT_EQ(vec1[3], 2);
EXPECT_EQ(vec1[4], 3);
}

TEST(VectorTest, EraseElemAtIterator) {
Vector<int> vec1 = {1, 2, 3};

auto pos = vec1.begin() + 1; // 2
vec1.erase(pos);

EXPECT_EQ(vec1.size(), 2);
EXPECT_EQ(vec1[0], 1);
EXPECT_EQ(vec1[1], 3);
}

TEST(VectorTest, EraseElemsInIteratorRange) {
Vector<int> vec1 = {1, 2, 3, 4, 5, 6, 7};

auto first = vec1.begin() + 2; // 3
auto last = vec1.begin() + 5; // 6
vec1.erase(first, last);

EXPECT_EQ(vec1.size(), 4);
EXPECT_EQ(vec1[0], 1);
EXPECT_EQ(vec1[1], 2);
EXPECT_EQ(vec1[2], 6);
EXPECT_EQ(vec1[3], 7);
}

TEST(VectorTest, VectorResizedSmaller) {
Vector<int> vec = {1, 2, 3, 4, 5, 6, 7};

vec.resize(3);

EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec.capacity(), 7);
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);
EXPECT_EQ(vec[2], 3);
}

TEST(VectorTest, VectorResizedLarger) {
Vector<int> vec = {1, 2, 3, 4, 5, 6, 7};

vec.resize(10);

EXPECT_EQ(vec.size(), 10);
EXPECT_EQ(vec.capacity(), 10);
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);
EXPECT_EQ(vec[2], 3);
EXPECT_EQ(vec[3], 4);
EXPECT_EQ(vec[4], 5);
EXPECT_EQ(vec[5], 6);
EXPECT_EQ(vec[6], 7);
EXPECT_EQ(vec[7], 0);
EXPECT_EQ(vec[8], 0);
EXPECT_EQ(vec[9], 0);
}

TEST(VectorTest, VectorResizedLargerWithFillValue) {
Vector<int> vec = {1, 2, 3, 4, 5, 6, 7};

vec.resize(10, 42);

EXPECT_EQ(vec.size(), 10);
EXPECT_EQ(vec.capacity(), 10);
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);
EXPECT_EQ(vec[2], 3);
EXPECT_EQ(vec[3], 4);
EXPECT_EQ(vec[4], 5);
EXPECT_EQ(vec[5], 6);
EXPECT_EQ(vec[6], 7);
EXPECT_EQ(vec[7], 42);
EXPECT_EQ(vec[8], 42);
EXPECT_EQ(vec[9], 42);
}

TEST(VectorTest, VectorShrinkToFitDoesNothing) {
Vector<int> vec = {1, 2, 3, 4, 5, 6, 7};

vec.shrink_to_fit();

EXPECT_EQ(vec.size(), 7);
EXPECT_EQ(vec.capacity(), 7);
}

TEST(VectorTest, VectorShrinkToFitShrinksCapacity) {
Vector<int> vec = {1};
EXPECT_EQ(vec.size(), 1);
EXPECT_EQ(vec.capacity(), 1);

/**
* above capacity should be == 1
* if I insert then capacity doubles therefore cap: 1 --> 2
* then insert again and cap becomes: 2 --> 4 but size is 3
* verify this is correct with gtest
*
* THEN run a shrink_to_fit. size should still be 3
* but cap becomes: 4 -> 3
*/
vec.push_back(2); // cap: 1 -> 2
vec.push_back(3); // cap: 2 -> 4
EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec.capacity(), 4);

vec.shrink_to_fit();

EXPECT_EQ(vec.size(), 3);
EXPECT_EQ(vec.capacity(), 3);
}

TEST(VectorTest, VectorSwap) {
Vector<int> vec1 = {1, 2, 3, 4, 5};
Vector<int> vec2 = {2, 4, 6};

vec1.swap(vec2);

EXPECT_EQ(vec1.size(), 3);
EXPECT_EQ(vec1.capacity(), 3);
EXPECT_EQ(vec1[0], 2);
EXPECT_EQ(vec1[1], 4);
EXPECT_EQ(vec1[2], 6);

EXPECT_EQ(vec2.size(), 5);
EXPECT_EQ(vec2.capacity(), 5);
EXPECT_EQ(vec2[0], 1);
EXPECT_EQ(vec2[1], 2);
EXPECT_EQ(vec2[2], 3);
EXPECT_EQ(vec2[3], 4);
EXPECT_EQ(vec2[4], 5);
}

TEST(VectorTest, VectorEqualityTest) {
Vector<int> vec1 = {1, 2, 3, 4, 5};
Vector<int> vec2 = {1, 2, 3, 4, 5};
Vector<int> vec3 = {2, 4, 6};

EXPECT_TRUE(vec1 == vec2);
EXPECT_FALSE(vec1 == vec3);
EXPECT_FALSE(vec2 == vec3);

EXPECT_FALSE(vec1 != vec2);
EXPECT_TRUE(vec1 != vec3);
EXPECT_TRUE(vec2 != vec3);
}

TEST(VectorTest, VectorCopyAssignmentOperator) {
Vector<int> vec1 = {1, 2, 3, 4, 5};
Vector<int> vec2 = {2, 4, 6};

vec1 = vec2;
EXPECT_EQ(vec1, vec2);
EXPECT_EQ(vec1[0], 2);
EXPECT_EQ(vec1[1], 4);
EXPECT_EQ(vec1[2], 6);
EXPECT_EQ(vec1.size(), 3);

EXPECT_EQ(vec2[0], 2);
EXPECT_EQ(vec2[1], 4);
EXPECT_EQ(vec2[2], 6);
EXPECT_EQ(vec2.size(), 3);
}

TEST(VectorTest, VectorMoveAssignmentOperator) {
Vector<int> vec1 = {1, 2, 3, 4, 5};
Vector<int> vec2 = {2, 4, 6};

vec1 = std::move(vec2);
EXPECT_NE(vec1, vec2);
EXPECT_EQ(vec1[0], 2);
EXPECT_EQ(vec1[1], 4);
EXPECT_EQ(vec1[2], 6);
EXPECT_EQ(vec1.size(), 3);

EXPECT_EQ(vec2.size(), 0);
EXPECT_EQ(vec2.capacity(), 0);
EXPECT_EQ(vec2.data(), nullptr);
}

Other notes about STL Implementation and/or C++