#pragma once
#include <algorithm>
#include <numeric>
#include <cassert>
#include <stdexcept>
namespace API
{
template<typename T>
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<size_type>(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<T> 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<T>& a, Container<T>& 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<typename T>
bool operator==(const Container<T>& lhs, const Container<T>& rhs) noexcept {
if (std::size(lhs) != std::size(rhs)) { return false; }
return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs));
}
template<typename T>
bool operator<(const Container<T>& lhs, const Container<T>& rhs) noexcept {
return std::lexicographical_compare(lhs.begin(), lhs.end(),
rhs.begin(), rhs.end());
}
template<typename T>
bool operator!=(const Container<T>& lhs, const Container<T>& rhs) noexcept {
return not(lhs == rhs);
}
template<typename T>
bool operator>(const Container<T>& lhs, const Container<T>& rhs) noexcept {
return rhs < lhs;
}
template<typename T>
bool operator<=(const Container<T>& lhs, const Container<T>& rhs) noexcept {
!(lhs > rhs);
}
template<typename T>
bool operator>=(const Container<T>& lhs, const Container<T>& rhs) noexcept {
!(lhs < rhs);
}
}
Comments
0 B
|0 👍
/0 👎