#pragma once #include #include #include #include namespace API { template class Container { public: using pointer = T*; using const_pointer = const T*; using iterator = T*; //aliases for generic code using const_iterator = const T*; using value_type = T; using size_type = size_t; using reference = T&; using const_reference = const T&; Container() = default; ~Container() { delete[] _data; } //WARNING: may invoke double-delete, if we've manually called destructors on our Ts. explicit Container(size_type count) : _data(new T[count]), _count(count), _capacity(count){ //STL behavior is to fill with defaulted-Ts. //std::fill(begin(), end(), T()); //I'm ignoring this to let me delegate to this constructor efficiently. }; //range construction Container(const_iterator begin, const_iterator end) : Container(static_cast(std::distance(begin, end))) { assert(begin <= end && "Container(iter, iter): begin & end iterators are reversed"); std::copy(begin, end, begin()); } //copy ctor, delegating to range explicit Container(const Container& that) : Container(that.begin(), that.end()){} //list construction, delegating to range. Container(std::initializer_list list) : Container(std::begin(list), std::end(list)) {} Container(Container&& that) noexcept { _count = std::exchange(that._count, 0); _capacity = std::exchange(that._capacity, 0); _data = std::exchange(that._data, nullptr); } //by value assignment idiom. deals with both copy- and move assignment Container& operator=(Container that) noexcept { swap(that); //we take all in that, and give that all of us. that is destroyed at the return of this function. return *this; } void swap(Container& that) noexcept { using std::swap; swap(_data, that._data); swap(_count, that._count); swap(_capacity, that._capacity); } //ADL overload. See Arthur O'Dwyer: https://youtu.be/7Qgd9B1KuMQ?t=2597 friend void swap(Container& a, Container& b) noexcept { a.swap(b); } void clear() noexcept { //TODO: could be erase(begin(), end()). while (!empty()) { pop_back(); } } void pop_back() noexcept { assert(!empty() && "pop_back() on empty container is undefined!"); back().~T(); //WARNING: will lead do double delete when we call delete[] on _data --_count; } void reserve(size_type newCapacity) { if (newCapacity <= _capacity) { return; } //never decrease the allocation pointer newBuffer = new T[newCapacity]; std::move(begin(), end(), newBuffer); delete[] _data; _data = newBuffer; _capacity = newCapacity; } void push_back(const T& value) { if (_capacity == 0) { reserve(2); } else if (_count == _capacity) { reserve(2 * _capacity); } _data[_count] = value; _count++; } reference at(size_type index) { if (index >= size()) { throw std::out_of_range("Container at() index is out of range!"); } return _data[index]; } const_reference at(size_type index) const { if (index >= size()) { throw std::out_of_range("Container at() index is out of range!"); } return _data[index]; } reference operator[](size_type index) noexcept{ assert(index < size() && "Container operator[] index is out of range!"); return _data[index]; } const_reference operator[](size_type index) const noexcept { assert(index < size() && "Container operator[] index is out of range!"); return _data[index]; } reference front() noexcept { assert(!empty() && "front() on empty container is UB"); return _data[0]; } const_reference front() const noexcept { assert(!empty() && "front() on empty container is UB"); return _data[0]; } const_reference back() const noexcept { assert(!empty() && "back() on empty container is UB"); return _data[size() - 1]; } reference back() noexcept { assert(!empty() && "back() on empty container is UB"); return _data[size() - 1]; } [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } size_type capacity() const noexcept { return _capacity; } size_type size() const noexcept { return _count; } //TODO: make free functions of these const_iterator begin() const noexcept { return _data; } const_iterator cbegin() const noexcept { return _data; } iterator begin() noexcept { return _data; } const_iterator end() const noexcept { return _data + _count; } const_iterator cend() const noexcept { return _data + _count; } iterator end() noexcept { return _data + _count; } private: T* _data = nullptr; size_type _count = 0; size_type _capacity = 0; }; template bool operator==(const Container& lhs, const Container& rhs) noexcept { if (std::size(lhs) != std::size(rhs)) { return false; } return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs)); } template bool operator<(const Container& lhs, const Container& rhs) noexcept { return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); } template bool operator!=(const Container& lhs, const Container& rhs) noexcept { return not(lhs == rhs); } template bool operator>(const Container& lhs, const Container& rhs) noexcept { return rhs < lhs; } template bool operator<=(const Container& lhs, const Container& rhs) noexcept { !(lhs > rhs); } template bool operator>=(const Container& lhs, const Container& rhs) noexcept { !(lhs < rhs); } }