/* K. Iglberger: Calling Functions: A Tutorial, CppCon 2020 Scott Meyers, Effective C++ 3rd Edition, Item 23 Scott Meyers: How Non-Member Functions Improve Encapsulation Herb Sutter: GotW #70: Encapsulation Herb Sutter, Exceptional C++ Style, Item 37 to Item 40 Online: GoTW#84: Monoliths "Unstrung" Herb Sutter: What's In a Class? - The Interface Principle */ #pragma once #include #include #include #include namespace API { template class Container { public: using pointer = T*; using const_pointer = const T*; using iterator = T*; using const_iterator = const T*; using value_type = T; using size_type = std::size_t; using reference = T&; using const_reference = const T&; Container() = default; ~Container() { clear(*this); ::operator delete(_data); } explicit Container(size_type capacity) : _data(static_cast(::operator new(sizeof(value_type) * capacity))) , _count(0) , _capacity(capacity) { //STL behavior is to fill with defaulted-Ts. //I'm ignoring that expection in order to delegate to this construction without paying for double-constructions. } Container(size_type count, const T& val) : Container(count) { std::uninitialized_fill_n(begin(*this), count, val); // copy construct each element w/ placement new _count = count; } //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::uninitialized_copy(begin, end, begin(*this)); // copy construct each element from range into buffer w/ placement new _count = static_cast(std::distance(begin, end)); } //copy ctor, delegating to range constructor explicit Container(const Container& that) : Container(that.begin(), that.end()) { } //list construction, delegating to range constructor Container(std::initializer_list list) : Container(std::begin(list), std::end(list)) { } //move constructor 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 //also known as: "unifying assignment operator" //https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap Container& operator=(Container that) noexcept { swap(that); return *this; } void swap(Container& that) noexcept { using std::swap; swap(_data, that._data); swap(_count, that._count); swap(_capacity, that._capacity); } void pop_back() noexcept { assert(!empty(*this) && "pop_back() on empty container is undefined!"); back(*this).~value_type(); --_count; } void reserve(size_type newCapacity){ if (newCapacity <= _capacity) { return; //never decrease the allocation } pointer newBuffer = static_cast(::operator new(sizeof(value_type) * newCapacity)); std::uninitialized_move(begin(*this), end(*this), newBuffer); destruct_all(); //run all destructors on the moved-from objects. ::operator delete(_data); _data = newBuffer; _capacity = newCapacity; } template void emplace_back(Args&&... args){ resizeIfNeeded(); new (&_data[_count]) value_type(std::forward(args)...); // construct value in memory of aligned storage using inplace operator new ++_count; } 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]; } pointer data() noexcept { return _data; } const_pointer data() const noexcept { return _data; } size_type capacity() const noexcept { return _capacity; } size_type size() const noexcept { return _count; } private: pointer _data = nullptr; size_type _count = 0; size_type _capacity = 0; constexpr static size_type INITIAL_CAPACITY = 2; constexpr static double GROWTH_FACTOR = 2.0f; void resizeIfNeeded() { if (_capacity == 0) { reserve(INITIAL_CAPACITY); } else if (_count == _capacity) { reserve(static_cast(GROWTH_FACTOR * _capacity)); } } void destruct_all() noexcept { for (reference item : *this) { item.~value_type(); } } }; template void push_back(Container& c, const T& value) { c.emplace_back(std::forward(value)); } template void clear(Container& c) noexcept { //TODO: could be erase(begin(), end()). while (!empty(c)) { c.pop_back(); } } //ADL overload. See Arthur O'Dwyer: https://youtu.be/7Qgd9B1KuMQ?t=2597 template void swap(Container& a, Container& b) noexcept{ a.swap(b); } template typename Container::reference at(Container& c, typename Container::size_type index) { if (index >= c.size()) { throw std::out_of_range("Container at() index is out of range!"); } return c.data()[index]; } template typename Container::const_reference at(const Container& c, typename Container::size_type index) { if (index >= c.size()) { throw std::out_of_range("Container at() index is out of range!"); } return c.data()[index]; } template typename Container::reference front(Container& c) noexcept { assert(!empty(c) && "front() on empty container is UB"); return c.data()[0]; } template typename Container::const_reference front(const Container& c) noexcept { assert(!empty(c) && "front() on empty container is UB"); return c.data()[0]; } template typename Container::const_reference back(const Container& c) noexcept { assert(!empty(c) && "back() on empty container is UB"); return c.data()[c.size() - 1]; } template typename Container::reference back(Container& c) noexcept { assert(!empty(c) && "back() on empty container is UB"); return c.data()[c.size() - 1]; } template bool empty(const Container& c) noexcept{ return c.size() == 0; } template typename Container::iterator begin(Container& c) noexcept { return c.data(); } template typename Container::const_iterator begin(const Container& c) noexcept { return c.data(); } template typename Container::const_iterator cbegin(const Container& c) noexcept { return c.data(); } template typename Container::const_iterator end(const Container& c) noexcept { return c.data() + c.size(); } template typename Container::const_iterator cend(const Container& c) noexcept { return c.data() + c.size(); } template typename Container::iterator end(Container& c) noexcept { return c.data() + c.size(); } 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 !(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); } }