diff --git a/yasio/array_buffer.hpp b/yasio/array_buffer.hpp new file mode 100644 index 00000000..0a6ff821 --- /dev/null +++ b/yasio/array_buffer.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "yasio/vector.hpp" +#include "yasio/buffer_alloc.hpp" +#include + +namespace yasio +{ +// alias: array_buffer +template > +using array_buffer = typename std::enable_if::value, ::yasio::vector<_Ty, _Alloc>>::type; +} // namespace yasio diff --git a/yasio/bindings/yasio_axlua.cpp b/yasio/bindings/yasio_axlua.cpp index ccd45528..208f2b4a 100644 --- a/yasio/bindings/yasio_axlua.cpp +++ b/yasio/bindings/yasio_axlua.cpp @@ -37,8 +37,11 @@ SOFTWARE. # if !__has_attribute(format) # undef __has_attribute # endif -#include "cocos2d.h" -using namespace cocos2d; + +#include "axmol/base/Director.h" +#include "axmol/base/Scheduler.h" + +using namespace ax; namespace lyasio { @@ -72,7 +75,7 @@ static TIMER_ID loop(unsigned int n, float interval, vcallback_t callback) auto timerId = reinterpret_cast(++TimerObject::s_timerId); - std::string key = StringUtils::format("LSTMR#%p", timerId); + std::string key = fmt::format("LSTMR#{}", fmt::ptr(timerId)); Director::getInstance()->getScheduler()->schedule( [timerObj]( @@ -93,7 +96,7 @@ static TIMER_ID delay(float delay, vcallback_t callback) yasio::ref_ptr timerObj(new TimerObject(std::move(callback))); auto timerId = reinterpret_cast(++TimerObject::s_timerId); - std::string key = StringUtils::format("LSTMR#%p", timerId); + std::string key = fmt::format("LSTMR#{}", fmt::ptr(timerId)); Director::getInstance()->getScheduler()->schedule( [timerObj]( float /*dt*/) { // lambda expression hold the reference of timerObj automatically. @@ -108,7 +111,7 @@ static TIMER_ID delay(float delay, vcallback_t callback) static void kill(TIMER_ID timerId) { - std::string key = StringUtils::format("LSTMR#%p", timerId); + std::string key = fmt::format("LSTMR#{}", fmt::ptr(timerId)); Director::getInstance()->getScheduler()->unschedule(key, STIMER_TARGET_VALUE); } YASIO_LUA_API void clear() diff --git a/yasio/buffer_alloc.hpp b/yasio/buffer_alloc.hpp index 9a4981b3..f18cf5e2 100644 --- a/yasio/buffer_alloc.hpp +++ b/yasio/buffer_alloc.hpp @@ -36,83 +36,51 @@ SOFTWARE. #include "yasio/compiler/feature_test.hpp" #include "yasio/type_traits.hpp" -#define _YASIO_VERIFY_RANGE(cond, mesg) \ - do \ - { \ - if (cond) \ - ; /* contextually convertible to bool paranoia */ \ - else \ - { \ - throw std::out_of_range(mesg); \ - } \ - \ - } while (false) - namespace yasio { -template -struct buffer_allocator_traits { - using value_type = typename _Alty::value_type; - using size_type = size_t; - static YASIO__CONSTEXPR size_type max_size() { return static_cast(-1) / sizeof(value_type); } - static value_type* reallocate(void* block, size_t size, size_t new_size) - { - return static_cast(_Alty::reallocate(block, size, new_size * sizeof(value_type))); - } - static void deallocate(void* block, size_t size) { _Alty::deallocate(block, size); } -}; -template ::value, int> = 0> -struct buffer_allocator { - using value_type = _Ty; - static value_type* reallocate(void* block, size_t /*size*/, size_t new_size) - { - return static_cast(::realloc(block, new_size * sizeof(value_type))); - } - static void deallocate(void* block, size_t /*size*/) { ::free(block); } -}; -template ::value, int> = 0> -struct std_buffer_allocator { - using value_type = _Ty; - static value_type* reallocate(void* block, size_t size, size_t new_size) - { - if (!block) - return new (std::nothrow) value_type[new_size]; - void* new_block = nullptr; - if (new_size) - { - if (new_size <= size) - return block; - new_block = new (std::nothrow) value_type[new_size]; - if (new_block) - memcpy(new_block, block, size); - } - delete[] (value_type*)block; - return (value_type*)new_block; - } - static void deallocate(void* block, size_t /*size*/) { delete[] (value_type*)block; } -}; -template -struct construct_helper { - template - static _Ty* construct_at(_Ty* p, Args&&... args) +template +struct crt_buffer_allocator { + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + // allocate n elements using CRT malloc + pointer allocate(size_type n) { - return ::new (static_cast(p)) _Ty(std::forward(args)...); + if (n > max_size()) + throw std::bad_alloc(); + void* p = ::malloc(n * sizeof(T)); + if (!p) + throw std::bad_alloc(); + return static_cast(p); } -}; -template -struct construct_helper<_Ty, false> { - template - static _Ty* construct_at(_Ty* p, Args&&... args) + + // deallocate memory using CRT free + void deallocate(pointer p, size_type /*n*/) noexcept { ::free(p); } + + // optional reallocate (non-standard, but useful for vector) + pointer reallocate(pointer p, size_type old_count, size_type new_count) { - return ::new (static_cast(p)) _Ty{std::forward(args)...}; + void* np = ::realloc(p, new_count * sizeof(T)); + if (!np) + throw std::bad_alloc(); + return static_cast(np); } -}; -template -inline _Ty* construct_at(_Ty* p, Args&&... args) -{ - return construct_helper<_Ty, std::is_constructible<_Ty, Args&&...>::value>::construct_at(p, std::forward(args)...); -} + // max_size consistent with std::allocator + size_type max_size() const noexcept { return static_cast(-1) / sizeof(T); } + + // equality operators required by standard + template + struct rebind { + using other = crt_buffer_allocator; + }; + + bool operator==(const crt_buffer_allocator&) const noexcept { return true; } + bool operator!=(const crt_buffer_allocator&) const noexcept { return false; } +}; } // namespace yasio diff --git a/yasio/byte_buffer.hpp b/yasio/byte_buffer.hpp index 75f44050..ecb2e138 100644 --- a/yasio/byte_buffer.hpp +++ b/yasio/byte_buffer.hpp @@ -27,13 +27,13 @@ SOFTWARE. */ #ifndef YASIO__BYTE_BUFFER_HPP #define YASIO__BYTE_BUFFER_HPP -#include "yasio/pod_vector.hpp" +#include "yasio/array_buffer.hpp" namespace yasio { - -template , enable_if_t::value, int> = 0> -using basic_byte_buffer = array_buffer<_Ty, _Alloc>; +// basic_byte_buffer restricted to byte types +template > +using basic_byte_buffer = typename std::enable_if::value, array_buffer<_Ty, _Alloc>>::type; using sbyte_buffer = basic_byte_buffer; using byte_buffer = basic_byte_buffer; diff --git a/yasio/impl/poll_io_watcher.hpp b/yasio/impl/poll_io_watcher.hpp index 7c65fd99..9e5ecb13 100644 --- a/yasio/impl/poll_io_watcher.hpp +++ b/yasio/impl/poll_io_watcher.hpp @@ -6,7 +6,7 @@ // Copyright (c) 2012-2025 HALX99 (halx99 at live dot com) #ifndef YASIO__POLL_IO_WATCHER_HPP #define YASIO__POLL_IO_WATCHER_HPP -#include "yasio/pod_vector.hpp" +#include "yasio/vector.hpp" #include "yasio/impl/socket.hpp" #include "yasio/impl/select_interrupter.hpp" @@ -77,7 +77,7 @@ class poll_io_watcher { } return underlying_events; } - static void pollfd_mod(yasio::pod_vector& fdset, socket_native_type fd, int add_events, int remove_events) + static void pollfd_mod(yasio::vector& fdset, socket_native_type fd, int add_events, int remove_events) { auto it = std::find_if(fdset.begin(), fdset.end(), [fd](const pollfd& pfd) { return pfd.fd == fd; }); if (it != fdset.end()) @@ -96,8 +96,8 @@ class poll_io_watcher { } protected: - yasio::pod_vector events_; - yasio::pod_vector revents_; + yasio::vector events_; + yasio::vector revents_; select_interrupter interrupter_; }; diff --git a/yasio/memory.hpp b/yasio/memory.hpp index 830f7670..15b1f25b 100644 --- a/yasio/memory.hpp +++ b/yasio/memory.hpp @@ -1,5 +1,5 @@ ////////////////////////////////////////////////////////////////////////////////////////// -// A multi-platform support c++11 library with focus on asynchronous socket I/O for any +// A multi-platform support c++11 library with focus on asynchronous socket I/O for any // client application. ////////////////////////////////////////////////////////////////////////////////////////// /* @@ -35,11 +35,151 @@ SOFTWARE. #if !YASIO__HAS_CXX14 namespace cxx14 { -template std::unique_ptr<_Ty> make_unique(_Args&&... args) +template +std::unique_ptr<_Ty> make_unique(_Args&&... args) { return std::unique_ptr<_Ty>(new _Ty(std::forward<_Args>(args)...)); } } // namespace cxx14 #endif +namespace yasio +{ +template +struct construct_helper { + template + static _Ty* construct_at(_Ty* p, Args&&... args) + { + return ::new (static_cast(p)) _Ty(std::forward(args)...); + } +}; +template +struct construct_helper<_Ty, false> { + template + static _Ty* construct_at(_Ty* p, Args&&... args) + { + return ::new (static_cast(p)) _Ty{std::forward(args)...}; + } +}; + +template +inline _Ty* construct_at(_Ty* p, Args&&... args) +{ + return construct_helper<_Ty, std::is_constructible<_Ty, Args&&...>::value>::construct_at(p, std::forward(args)...); +} + +/** compressed_pair + * stores two objects, but applies Empty Base Optimization(EBO) when one of them is an empty type. + * This reduces memory overhead. + */ +template && !std::is_final_v<_Ty1>, bool = std::is_empty_v<_Ty2> && !std::is_final_v<_Ty2>> +class compressed_pair; + +// Case 1: neither empty +template +class compressed_pair<_Ty1, _Ty2, false, false> { + _Ty1 _Myval1; + _Ty2 _Myval2; + +public: + // constructors + constexpr compressed_pair() = default; + + constexpr compressed_pair(const _Ty1& v1, const _Ty2& v2) noexcept(std::is_nothrow_copy_constructible_v<_Ty1> && std::is_nothrow_copy_constructible_v<_Ty2>) + : _Myval1(v1), _Myval2(v2) + {} + + constexpr compressed_pair(_Ty1&& v1, _Ty2&& v2) noexcept(std::is_nothrow_move_constructible_v<_Ty1> && std::is_nothrow_move_constructible_v<_Ty2>) + : _Myval1(std::move(v1)), _Myval2(std::move(v2)) + {} + + template + constexpr compressed_pair(U1&& v1, U2&& v2) noexcept(std::is_nothrow_constructible_v<_Ty1, U1&&> && std::is_nothrow_constructible_v<_Ty2, U2&&>) + : _Myval1(std::forward(v1)), _Myval2(std::forward(v2)) + {} + + // accessors + constexpr _Ty1& first() noexcept { return _Myval1; } + constexpr const _Ty1& first() const noexcept { return _Myval1; } + constexpr _Ty2& second() noexcept { return _Myval2; } + constexpr const _Ty2& second() const noexcept { return _Myval2; } +}; + +// Case 2: First empty +template +class compressed_pair<_Ty1, _Ty2, true, false> : private _Ty1 { + _Ty2 _Myval2; + +public: + constexpr compressed_pair() = default; + + constexpr compressed_pair(const _Ty1& v1, const _Ty2& v2) noexcept(std::is_nothrow_copy_constructible_v<_Ty2>) : _Ty1(v1), _Myval2(v2) {} + + constexpr compressed_pair(_Ty1&& v1, _Ty2&& v2) noexcept(std::is_nothrow_move_constructible_v<_Ty2>) : _Ty1(std::move(v1)), _Myval2(std::move(v2)) {} + + template + constexpr compressed_pair(U1&& v1, U2&& v2) noexcept(std::is_nothrow_constructible_v<_Ty2, U2&&>) : _Ty1(std::forward(v1)), _Myval2(std::forward(v2)) + {} + + constexpr _Ty1& first() noexcept { return *this; } + constexpr const _Ty1& first() const noexcept { return *this; } + constexpr _Ty2& second() noexcept { return _Myval2; } + constexpr const _Ty2& second() const noexcept { return _Myval2; } +}; + +// Case 3: Second empty +template +class compressed_pair<_Ty1, _Ty2, false, true> : private _Ty2 { + _Ty1 _Myval1; + +public: + constexpr compressed_pair() = default; + + constexpr compressed_pair(const _Ty1& v1, const _Ty2& v2) noexcept(std::is_nothrow_copy_constructible_v<_Ty1>) : _Myval1(v1), _Ty2(v2) {} + + constexpr compressed_pair(_Ty1&& v1, _Ty2&& v2) noexcept(std::is_nothrow_move_constructible_v<_Ty1>) : _Myval1(std::move(v1)), _Ty2(std::move(v2)) {} + + template + constexpr compressed_pair(U1&& v1, U2&& v2) noexcept(std::is_nothrow_constructible_v<_Ty1, U1&&>) : _Myval1(std::forward(v1)), _Ty2(std::forward(v2)) + {} + + constexpr _Ty1& first() noexcept { return _Myval1; } + constexpr const _Ty1& first() const noexcept { return _Myval1; } + constexpr _Ty2& second() noexcept { return *this; } + constexpr const _Ty2& second() const noexcept { return *this; } +}; + +// Case 4: both empty +template +class compressed_pair<_Ty1, _Ty2, true, true> : private _Ty1, private _Ty2 { +public: + constexpr compressed_pair() = default; + + constexpr compressed_pair(const _Ty1& v1, const _Ty2& v2) : _Ty1(v1), _Ty2(v2) {} + + constexpr compressed_pair(_Ty1&& v1, _Ty2&& v2) : _Ty1(std::move(v1)), _Ty2(std::move(v2)) {} + + template + constexpr compressed_pair(U1&& v1, U2&& v2) : _Ty1(std::forward(v1)), _Ty2(std::forward(v2)) + {} + + constexpr _Ty1& first() noexcept { return *this; } + constexpr const _Ty1& first() const noexcept { return *this; } + constexpr _Ty2& second() noexcept { return *this; } + constexpr const _Ty2& second() const noexcept { return *this; } +}; +} // namespace yasio + +#define _YASIO_VERIFY_RANGE(cond, mesg) \ + do \ + { \ + if (cond) \ + ; /* contextually convertible to bool paranoia */ \ + else \ + { \ + throw std::out_of_range(mesg); \ + } \ + \ + } while (false) + #endif diff --git a/yasio/pod_vector.hpp b/yasio/pod_vector.hpp deleted file mode 100644 index 901c1e6a..00000000 --- a/yasio/pod_vector.hpp +++ /dev/null @@ -1,516 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// -// A multi-platform support c++11 library with focus on asynchronous socket I/O for any -// client application. -////////////////////////////////////////////////////////////////////////////////////////// -/* -The MIT License (MIT) - -Copyright (c) 2012-2025 HALX99 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -Version: 4.3.2 - -The pod_vector aka array_buffer concepts: - a. The memory model is similar to to std::vector, but only accept trivially_copyable(no destructor & no custom copy constructor) types - b. The resize behavior differrent stl, always allocate exactly - c. By default resize without fill (uninitialized and for overwrite), - use insert/append insetad if you want fill memory inside container - d. Support release internal buffer ownership with `release_pointer` - e. Transparent iterator - f. expand/append/insert/push_back will trigger memory allocate growth strategy MSVC - g. resize_and_overwrite (c++23) -*/ -#ifndef YASIO__POD_VECTOR_HPP -#define YASIO__POD_VECTOR_HPP -#include -#include -#include -#include -#include -#include "yasio/buffer_alloc.hpp" -#include "yasio/compiler/feature_test.hpp" - -namespace yasio -{ -template > -class pod_vector { -public: - using pointer = _Ty*; - using const_pointer = const _Ty*; - using reference = _Ty&; - using const_reference = const _Ty&; - using _Alloc_traits = buffer_allocator_traits<_Alloc>; - using size_type = typename _Alloc_traits::size_type; - using value_type = _Ty; - using iterator = _Ty*; // transparent iterator - using const_iterator = const _Ty*; - using allocator_type = _Alloc; - pod_vector() {} - explicit pod_vector(size_type count) { resize(static_cast(count)); } - pod_vector(size_type count, const_reference val) { resize(static_cast(count), val); } - template ::value, int> = 0> - pod_vector(_Iter first, _Iter last) - { - assign(first, last); - } - pod_vector(const pod_vector& rhs) { assign(rhs); }; - pod_vector(pod_vector&& rhs) YASIO__NOEXCEPT { assign(std::move(rhs)); } - /*pod_vector(std::initializer_list rhs) { _Assign_range(rhs.begin(), rhs.end()); }*/ - ~pod_vector() { _Tidy(); } - pod_vector& operator=(const pod_vector& rhs) - { - assign(rhs); - return *this; - } - pod_vector& operator=(pod_vector&& rhs) YASIO__NOEXCEPT - { - this->swap(rhs); - return *this; - } - template - pod_vector& operator+=(const _Cont& rhs) - { - return this->append(std::begin(rhs), std::end(rhs)); - } - pod_vector& operator+=(const_reference rhs) - { - this->push_back(rhs); - return *this; - } - template ::value, int> = 0> - void assign(_Iter first, _Iter last) - { - _Assign_range(first, last); - } - void assign(const pod_vector& rhs) { _Assign_range(rhs.begin(), rhs.end()); } - void assign(pod_vector&& rhs) { _Assign_rv(std::move(rhs)); } - void swap(pod_vector& rhs) YASIO__NOEXCEPT - { - std::swap(_Myfirst, rhs._Myfirst); - std::swap(_Mysize, rhs._Mysize); - std::swap(_Myres, rhs._Myres); - } - template ::value, int> = 0> - iterator insert(iterator pos, _Iter first, _Iter last) - { - auto mlast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE(pos >= _Myfirst && pos <= mlast && first <= last, "pod_vector: out of range!"); - if (first != last) - { - auto insertion_off = static_cast(std::distance(_Myfirst, pos)); - if (pos == mlast) - append(first, last); - else - { - auto ifirst = std::addressof(*first); - static_assert(sizeof(*ifirst) == sizeof(value_type), "pod_vector: iterator type incompatible!"); - auto count = static_cast(std::distance(first, last)); - if (insertion_off >= 0) - { - expand(count); - pos = _Myfirst + insertion_off; - mlast = _Myfirst + _Mysize; - auto move_to = pos + count; - std::copy_n(pos, mlast - move_to, move_to); - std::copy_n((iterator)ifirst, count, pos); - } - } - return _Myfirst + insertion_off; - } - return pos; - } - iterator insert(iterator pos, size_type count, const_reference val) - { - auto mlast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE(pos >= _Myfirst && pos <= mlast, "pod_vector: out of range!"); - if (count) - { - auto insertion_off = std::distance(_Myfirst, pos); - if (pos == mlast) - append(count, val); - else - { - if (insertion_off >= 0) - { - const auto old_size = _Mysize; - expand(count); - pos = _Myfirst + insertion_off; - mlast = _Myfirst + _Mysize; - auto move_to = pos + count; - std::copy_n(pos, mlast - move_to, move_to); - std::fill_n(pos, count, val); - } - } - return _Myfirst + insertion_off; - } - return pos; - } - iterator insert(iterator pos, const value_type& val) - { // insert val at pos - return emplace(pos, val); - } - iterator insert(iterator pos, value_type&& val) - { // insert by moving val at pos - return emplace(pos, std::move(val)); - } - template - iterator emplace(iterator pos, _Valty&&... val) - { - auto insertion_off = std::distance(_Myfirst, pos); - _YASIO_VERIFY_RANGE(insertion_off <= _Mysize, "pod_vector: out of range!"); -#if YASIO__HAS_CXX20 - emplace_back(std::forward<_Valty>(val)...); - std::rotate(begin() + insertion_off, end() - 1, end()); - return (begin() + insertion_off); -#else - auto mlast = _Myfirst + _Mysize; - if (pos == mlast) - emplace_back(std::forward<_Valty>(val)...); - else - { - if (insertion_off >= 0) - { - expand(1); - pos = _Myfirst + insertion_off; - mlast = _Myfirst + _Mysize; - auto move_to = pos + 1; - std::copy_n(pos, mlast - move_to, move_to); - ::yasio::construct_at(pos, std::forward<_Valty>(val)...); - } - } - return _Myfirst + insertion_off; -#endif - } - template ::value, int> = 0> - pod_vector& append(_Iter first, const _Iter last) - { - if (first != last) - { - auto ifirst = std::addressof(*first); - static_assert(sizeof(*ifirst) == sizeof(value_type), "pod_vector: iterator type incompatible!"); - auto count = static_cast(std::distance(first, last)); - if (count > 1) - { - const auto old_size = _Mysize; - expand(count); - std::copy_n((iterator)ifirst, count, _Myfirst + old_size); - } - else if (count == 1) - push_back(static_cast(*(iterator)ifirst)); - } - return *this; - } - pod_vector& append(size_type count, const_reference val) - { - expand(count, val); - return *this; - } - void push_back(value_type&& val) { push_back(val); } - void push_back(const value_type& val) { emplace_back(val); } - void pop_back() - { - if (!empty()) - _Eos(size() - 1); - } - template - inline value_type& emplace_back(_Valty&&... val) - { - if (_Mysize < _Myres) - return *::yasio::construct_at(_Myfirst + _Mysize++, std::forward<_Valty>(val)...); - return *_Emplace_back_reallocate(std::forward<_Valty>(val)...); - } - iterator erase(iterator pos) - { - const auto mlast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE(pos >= _Myfirst && pos < mlast, "pod_vector: out of range!"); - _Mysize = static_cast(std::move(pos + 1, mlast, pos) - _Myfirst); - return pos; - } - iterator erase(iterator first, iterator last) - { - const auto mlast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE((first <= last) && first >= _Myfirst && last <= mlast, "pod_vector: out of range!"); - _Mysize = static_cast(std::move(last, mlast, first) - _Myfirst); - return first; - } - value_type& front() - { - _YASIO_VERIFY_RANGE(!empty(), "pod_vector: out of range!"); - return *_Myfirst; - } - const value_type& front() const - { - _YASIO_VERIFY_RANGE(!empty(), "pod_vector: out of range!"); - return *_Myfirst; - } - value_type& back() - { - _YASIO_VERIFY_RANGE(!empty(), "pod_vector: out of range!"); - return _Myfirst[_Mysize - 1]; - } - const value_type& back() const - { - _YASIO_VERIFY_RANGE(!empty(), "pod_vector: out of range!"); - return _Myfirst[_Mysize - 1]; - } - static YASIO__CONSTEXPR size_type max_size() YASIO__NOEXCEPT { return _Alloc_traits::max_size(); } - iterator begin() YASIO__NOEXCEPT { return _Myfirst; } - iterator end() YASIO__NOEXCEPT { return _Myfirst + _Mysize; } - const_iterator begin() const YASIO__NOEXCEPT { return _Myfirst; } - const_iterator end() const YASIO__NOEXCEPT { return _Myfirst + _Mysize; } - pointer data() YASIO__NOEXCEPT { return _Myfirst; } - const_pointer data() const YASIO__NOEXCEPT { return _Myfirst; } - size_type capacity() const YASIO__NOEXCEPT { return _Myres; } - size_type size() const YASIO__NOEXCEPT { return _Mysize; } - size_type length() const YASIO__NOEXCEPT { return _Mysize; } - void clear() YASIO__NOEXCEPT { _Mysize = 0; } - bool empty() const YASIO__NOEXCEPT { return _Mysize == 0; } - - const_reference operator[](size_type index) const { return this->at(index); } - reference operator[](size_type index) { return this->at(index); } - const_reference at(size_type index) const - { - _YASIO_VERIFY_RANGE(index < this->size(), "pod_vector: out of range!"); - return _Myfirst[index]; - } - reference at(size_type index) - { - _YASIO_VERIFY_RANGE(index < this->size(), "pod_vector: out of range!"); - return _Myfirst[index]; - } -#pragma region modify size and capacity - void resize(size_type new_size) - { - if (this->capacity() < new_size) - _Resize_reallocate<_Reallocation_policy::_Exactly>(new_size); - else - _Eos(new_size); - } - void expand(size_type count) - { - const auto new_size = this->size() + count; - if (this->capacity() < new_size) - _Resize_reallocate<_Reallocation_policy::_At_least>(new_size); - else - _Eos(new_size); - } - void shrink_to_fit() - { // reduce capacity to size, provide strong guarantee - if (_Mysize != _Myres) - { // something to do - if (!_Mysize) - _Tidy(); - else - _Reallocate<_Reallocation_policy::_Exactly>(_Mysize); - } - } - void reserve(size_type new_cap) - { - if (this->capacity() < new_cap) - _Reallocate<_Reallocation_policy::_Exactly>(new_cap); - } - template - void resize_and_overwrite(const size_type new_size, _Operation op) - { - _Reallocate<_Reallocation_policy::_Exactly>(new_size); - _Eos(std::move(op)(_Myfirst, new_size)); - } -#pragma endregion - void resize(size_type new_size, const_reference val) - { - auto old_size = this->size(); - if (old_size != new_size) - { - resize(new_size); - if (old_size < new_size) - std::fill_n(_Myfirst + old_size, new_size - old_size, val); - } - } - void expand(size_type count, const_reference val) - { - if (count) - { - auto old_size = this->size(); - expand(count); - if (count) - std::fill_n(_Myfirst + old_size, count, val); - } - } - ptrdiff_t index_of(const_reference val) const YASIO__NOEXCEPT - { - auto it = std::find(begin(), end(), val); - if (it != this->end()) - return std::distance(begin(), it); - return -1; - } - void reset(size_type new_size) - { - resize(new_size); - memset(_Myfirst, 0x0, size_bytes()); - } - size_t size_bytes() const YASIO__NOEXCEPT { return this->size() * sizeof(value_type); } - template - pointer detach_abi(_Intty& len) YASIO__NOEXCEPT - { - len = static_cast<_Intty>(this->size()); - auto ptr = _Myfirst; - _Myfirst = nullptr; - _Mysize = _Myres = 0; - return ptr; - } - pointer detach_abi() YASIO__NOEXCEPT - { - size_type ignored_len; - return this->detach_abi(ignored_len); - } - void attach_abi(pointer ptr, size_type len) - { - _Tidy(); - _Myfirst = ptr; - _Mysize = _Myres = len; - } - pointer release_pointer() YASIO__NOEXCEPT { return detach_abi(); } - -private: - void _Eos(size_type size) YASIO__NOEXCEPT { _Mysize = size; } - template - pointer _Emplace_back_reallocate(_Valty&&... val) - { - const auto _Oldsize = _Mysize; - - if (_Oldsize == max_size()) - throw std::length_error("pod_vector too long"); - - const size_type _Newsize = _Oldsize + 1; - _Resize_reallocate<_Reallocation_policy::_At_least>(_Newsize); - const pointer _Newptr = ::yasio::construct_at(_Myfirst + _Oldsize, std::forward<_Valty>(val)...); - return _Newptr; - } - template ::value, int> = 0> - void _Assign_range(_Iter first, _Iter last) - { - auto ifirst = std::addressof(*first); - static_assert(sizeof(*ifirst) == sizeof(value_type), "pod_vector: iterator type incompatible!"); - if (ifirst != _Myfirst) - { - _Mysize = 0; - if (last > first) - { - const auto count = static_cast(std::distance(first, last)); - resize(count); - std::copy_n((iterator)ifirst, count, _Myfirst); - } - } - } - void _Assign_rv(pod_vector&& rhs) - { - memcpy((void*)this, &rhs, sizeof(rhs)); - memset((void*)&rhs, 0, sizeof(rhs)); - } - enum class _Reallocation_policy - { - _At_least, - _Exactly - }; - template <_Reallocation_policy _Policy> - void _Resize_reallocate(size_type size) - { - _Reallocate<_Policy>(size); - _Eos(size); - } - template <_Reallocation_policy _Policy> - void _Reallocate(size_type size) - { - size_type new_cap; - if YASIO__CONSTEXPR (_Policy == _Reallocation_policy::_Exactly) - new_cap = size; - else - new_cap = _Calculate_growth(size); - auto _Newvec = _Alloc::reallocate(_Myfirst, _Myres, new_cap); - if (_Newvec) - { - _Myfirst = _Newvec; - _Myres = new_cap; - } - else - throw std::bad_alloc{}; - } - size_type _Calculate_growth(const size_type new_size) const - { - // given _Oldcapacity and _Newsize, calculate geometric growth - const size_type old_cap = capacity(); - YASIO__CONSTEXPR auto max_cap = max_size(); - - if (old_cap > max_cap - old_cap / 2) - return max_cap; // geometric growth would overflow - - const size_type geometric = old_cap + (old_cap >> 1); - - if (geometric < new_size) - return new_size; // geometric growth would be insufficient - - return geometric; // geometric growth is sufficient - } - void _Tidy() YASIO__NOEXCEPT - { // free all storage - if (_Myfirst) - { - _Alloc::deallocate(_Myfirst, _Myres); - _Myfirst = nullptr; - _Mysize = _Myres = 0; - } - } - - pointer _Myfirst = nullptr; - size_type _Mysize = 0; - size_type _Myres = 0; -}; - -#pragma region c++20 like std::erase -template -void erase(pod_vector<_Ty, _Alloc>& cont, const _Ty& val) -{ - cont.erase(std::remove(cont.begin(), cont.end(), val), cont.end()); -} -template -void erase_if(pod_vector<_Ty, _Alloc>& cont, _Pr pred) -{ - cont.erase(std::remove_if(cont.begin(), cont.end(), pred), cont.end()); -} -#pragma endregion - -template -inline typename _Cont::iterator insert_sorted(_Cont& vec, typename _Cont::value_type const& val) -{ - return vec.insert(std::upper_bound(vec.begin(), vec.end(), val), val); -} - -template -inline typename _Cont::iterator insert_sorted(_Cont& vec, typename _Cont::value_type const& val, _Pred pred) -{ - return vec.insert(std::upper_bound(vec.begin(), vec.end(), val, pred), val); -} - -// alias: array_buffer -template > -using array_buffer = pod_vector<_Ty, _Alloc>; - -} // namespace yasio -#endif diff --git a/yasio/string.hpp b/yasio/string.hpp index bd4044a2..8e571c4e 100644 --- a/yasio/string.hpp +++ b/yasio/string.hpp @@ -1,4 +1,3 @@ - ////////////////////////////////////////////////////////////////////////////////////////// // A multi-platform support c++11 library with focus on asynchronous socket I/O for any // client application. @@ -47,12 +46,13 @@ The yasio dedicated string (API not 100% compatible with stl) concepts: #include #include #include -#include "yasio/buffer_alloc.hpp" +#include "yasio/buffer_alloc.hpp" // crt_buffer_allocator #include "yasio/string_view.hpp" +#include "yasio/memory.hpp" // compressed_pair namespace yasio { -template , enable_if_t::value, int> = 0> +template , enable_if_t::value, int> = 0> class basic_string { public: using pointer = _Elem*; @@ -63,12 +63,21 @@ class basic_string { using iterator = _Elem*; // transparent iterator using const_iterator = const _Elem*; using allocator_type = _Alloc; - using _Alloc_traits = buffer_allocator_traits<_Alloc>; + using _Alloc_traits = std::allocator_traits<_Alloc>; using size_type = typename _Alloc_traits::size_type; using _Traits = std::char_traits<_Elem>; using view_type = cxx17::basic_string_view<_Elem>; using my_type = basic_string<_Elem, _Alloc>; - static const size_t npos = -1; + static const size_t npos = static_cast(-1); + + // compressed storage: allocator + 3 pointers (_Myfirst, _Mylast, _Myend) + struct _Str_storage { + pointer _Myfirst = nullptr; // begin + pointer _Mylast = nullptr; // one past last character + pointer _Myend = nullptr; // one past end of storage + }; + ::yasio::compressed_pair _Mypair; + basic_string() {} basic_string(nullptr_t) = delete; explicit basic_string(size_type count) { resize(static_cast(count)); } @@ -78,15 +87,17 @@ class basic_string { { assign(first, last); } - basic_string(const basic_string& rhs) { assign(rhs); }; - basic_string(basic_string&& rhs) YASIO__NOEXCEPT { assign(std::move(rhs)); } + basic_string(const basic_string& rhs) { assign(rhs); } + basic_string(basic_string&& rhs) YASIO__NOEXCEPT { _Assign_rv(std::move(rhs)); } basic_string(view_type rhs) { assign(rhs); } basic_string(const_pointer ntcs) { assign(ntcs); } basic_string(const_pointer ntcs, size_type count) { assign(ntcs, ntcs + count); } /*basic_string(std::initializer_list rhs) { _Assign_range(rhs.begin(), rhs.end()); }*/ ~basic_string() { _Tidy(); } + operator view_type() const YASIO__NOEXCEPT { return this->view(); } view_type view() const YASIO__NOEXCEPT { return view_type(this->c_str(), this->size()); } + basic_string& operator=(const basic_string& rhs) { assign(rhs); @@ -97,6 +108,7 @@ class basic_string { this->swap(rhs); return *this; } + template basic_string& operator+=(const _Cont& rhs) { @@ -107,6 +119,7 @@ class basic_string { this->push_back(rhs); return *this; } + template ::value, int> = 0> void assign(_Iter first, _Iter last) { @@ -117,20 +130,25 @@ class basic_string { void assign(const_pointer ntcs, size_type count) { _Assign_range(ntcs, ntcs + count); } void assign(const basic_string& rhs) { _Assign_range(rhs.begin(), rhs.end()); } void assign(basic_string&& rhs) { _Assign_rv(std::move(rhs)); } + void swap(basic_string& rhs) YASIO__NOEXCEPT { - std::swap(_Myfirst, rhs._Myfirst); - std::swap(_Mysize, rhs._Mysize); - std::swap(_Myres, rhs._Myres); + auto& a = _Mypair.second(); + auto& b = rhs._Mypair.second(); + std::swap(a._Myfirst, b._Myfirst); + std::swap(a._Mylast, b._Mylast); + std::swap(a._Myend, b._Myend); } + template ::value, int> = 0> iterator insert(iterator _Where, _Iter first, _Iter last) { - auto _Mylast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE(_Where >= _Myfirst && _Where <= _Mylast && first <= last, "basic_string: out of range!"); + auto& st = _Mypair.second(); + auto _Mylast = st._Mylast; + _YASIO_VERIFY_RANGE(_Where >= st._Myfirst && _Where <= _Mylast && first <= last, "basic_string: out of range!"); if (first != last) { - auto insertion_pos = static_cast(std::distance(_Myfirst, _Where)); + auto insertion_pos = static_cast(std::distance(st._Myfirst, _Where)); if (_Where == _Mylast) append(first, last); else @@ -140,131 +158,173 @@ class basic_string { auto count = static_cast(std::distance(first, last)); if (insertion_pos >= 0) { - auto old_size = _Mylast - _Myfirst; + auto old_size = static_cast(_Mylast - st._Myfirst); expand(count); - _Where = _Myfirst + insertion_pos; - _Mylast = _Myfirst + _Mysize; + _Where = st._Myfirst + insertion_pos; + _Mylast = st._Mylast; auto move_to = _Where + count; - std::copy_n(_Where, _Mylast - move_to, move_to); + std::copy_n(_Where, static_cast(_Mylast - move_to), move_to); std::copy_n((iterator)ifirst, count, _Where); } } - return _Myfirst + insertion_pos; + return st._Myfirst + insertion_pos; } return _Where; } + iterator insert(iterator _Where, size_type count, const_reference val) { - auto _Mylast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE(_Where >= _Myfirst && _Where <= _Mylast, "basic_string: out of range!"); + auto& st = _Mypair.second(); + auto _Mylast = st._Mylast; + _YASIO_VERIFY_RANGE(_Where >= st._Myfirst && _Where <= _Mylast, "basic_string: out of range!"); if (count) { - auto insertion_pos = std::distance(_Myfirst, _Where); + auto insertion_pos = std::distance(st._Myfirst, _Where); if (_Where == _Mylast) append(count, val); else { if (insertion_pos >= 0) { - const auto old_size = _Mysize; + const auto old_size = size(); expand(count); - _Where = _Myfirst + insertion_pos; - _Mylast = _Myfirst + _Mysize; + _Where = st._Myfirst + insertion_pos; + _Mylast = st._Mylast; auto move_to = _Where + count; - std::copy_n(_Where, _Mylast - move_to, move_to); + std::copy_n(_Where, static_cast(_Mylast - move_to), move_to); std::fill_n(_Where, count, val); } } - return _Myfirst + insertion_pos; + return st._Myfirst + insertion_pos; } return _Where; } + basic_string& append(view_type value) { return this->append(value.begin(), value.end()); } template ::value, int> = 0> basic_string& append(_Iter first, const _Iter last) { if (first != last) { + auto& st = _Mypair.second(); auto ifirst = std::addressof(*first); static_assert(sizeof(*ifirst) == sizeof(value_type), "basic_string: iterator type incompatible!"); auto count = static_cast(std::distance(first, last)); if (count > 1) { - const auto old_size = _Mysize; + const auto old_size = size(); expand(count); - std::copy_n((iterator)ifirst, count, _Myfirst + old_size); + std::copy_n((iterator)ifirst, count, st._Myfirst + old_size); } else if (count == 1) push_back(static_cast(*(iterator)ifirst)); } return *this; } + basic_string& append(size_type count, const_reference val) { expand(count, val); return *this; } + void push_back(value_type&& v) { push_back(v); } void push_back(const value_type& v) { expand(1); back() = v; } + iterator erase(iterator _Where) { - const auto _Mylast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE(_Where >= _Myfirst && _Where < _Mylast, "basic_string: out of range!"); - _Mysize = static_cast(std::move(_Where + 1, _Mylast, _Where) - _Myfirst); + auto& st = _Mypair.second(); + const auto _Mylast = st._Mylast; + _YASIO_VERIFY_RANGE(_Where >= st._Myfirst && _Where < _Mylast, "basic_string: out of range!"); + st._Mylast = std::move(_Where + 1, _Mylast, _Where); + // keep null terminator + *_Mylast_ptr() = value_type(0); return _Where; } + iterator erase(iterator first, iterator last) { - const auto _Mylast = _Myfirst + _Mysize; - _YASIO_VERIFY_RANGE((first <= last) && first >= _Myfirst && last <= _Mylast, "basic_string: out of range!"); - _Mysize = static_cast(std::move(last, _Mylast, first) - _Myfirst); + auto& st = _Mypair.second(); + const auto _Mylast = st._Mylast; + _YASIO_VERIFY_RANGE((first <= last) && first >= st._Myfirst && last <= _Mylast, "basic_string: out of range!"); + st._Mylast = std::move(last, _Mylast, first); + // keep null terminator + *_Mylast_ptr() = value_type(0); return first; } + value_type& front() { _YASIO_VERIFY_RANGE(!empty(), "basic_string: out of range!"); - return *_Myfirst; + return *_Mypair.second()._Myfirst; } + value_type& back() { _YASIO_VERIFY_RANGE(!empty(), "basic_string: out of range!"); - return _Myfirst[_Mysize - 1]; + return *(_Mypair.second()._Mylast - 1); + } + + static YASIO__CONSTEXPR size_type max_size() YASIO__NOEXCEPT + { + // static form to avoid needing allocator instance + return static_cast(-1) / sizeof(value_type); } - static YASIO__CONSTEXPR size_type max_size() YASIO__NOEXCEPT { return _Alloc_traits::max_size(); } #pragma region Iterators iterator begin() YASIO__NOEXCEPT { return this->data(); } - iterator end() YASIO__NOEXCEPT { return begin() + _Mysize; } + iterator end() YASIO__NOEXCEPT { return _Mypair.second()._Mylast; } const_iterator begin() const YASIO__NOEXCEPT { return this->data(); } - const_iterator end() const YASIO__NOEXCEPT { return begin() + _Mysize; } + const_iterator end() const YASIO__NOEXCEPT { return _Mypair.second()._Mylast; } #pragma endregion - pointer data() YASIO__NOEXCEPT { return _Myfirst; } - const_pointer data() const YASIO__NOEXCEPT { return _Myfirst; } - const_pointer c_str() const YASIO__NOEXCEPT { return _Myfirst ? _Myfirst : reinterpret_cast(&_Myfirst);; } + pointer data() YASIO__NOEXCEPT { return _Mypair.second()._Myfirst; } + const_pointer data() const YASIO__NOEXCEPT { return _Mypair.second()._Myfirst; } + + const_pointer c_str() const YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + return st._Myfirst ? st._Myfirst : reinterpret_cast(&st._Myfirst); + } + const_reference operator[](size_type index) const { return this->at(index); } reference operator[](size_type index) { return this->at(index); } const_reference at(size_type index) const { _YASIO_VERIFY_RANGE(index < this->size(), "basic_string: out of range!"); - return _Myfirst[index]; + return _Mypair.second()._Myfirst[index]; } reference at(size_type index) { _YASIO_VERIFY_RANGE(index < this->size(), "basic_string: out of range!"); - return _Myfirst[index]; + return _Mypair.second()._Myfirst[index]; } #pragma region Capacity - size_type capacity() const YASIO__NOEXCEPT { return _Myres; } - size_type size() const YASIO__NOEXCEPT { return _Mysize; } - size_type length() const YASIO__NOEXCEPT { return _Mysize; } - void clear() YASIO__NOEXCEPT { _Mysize = 0; } - bool empty() const YASIO__NOEXCEPT { return _Mysize == 0; } + size_type capacity() const YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + return static_cast(st._Myend - st._Myfirst); + } + size_type size() const YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + return static_cast(st._Mylast - st._Myfirst); + } + size_type length() const YASIO__NOEXCEPT { return size(); } + void clear() YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + st._Mylast = st._Myfirst; + if (st._Myfirst) + *st._Mylast = value_type(0); + } + bool empty() const YASIO__NOEXCEPT { return _Mypair.second()._Mylast == _Mypair.second()._Myfirst; } + void resize(size_type new_size) { if (this->capacity() < new_size) @@ -272,6 +332,7 @@ class basic_string { else _Eos(new_size); } + void expand(size_type count) { const auto new_size = this->size() + count; @@ -280,28 +341,36 @@ class basic_string { else _Eos(new_size); } + void shrink_to_fit() { // reduce capacity to size, provide strong guarantee - if (_Mysize != _Myres) + auto& st = _Mypair.second(); + if (st._Mylast != st._Myend) { // something to do - if (!_Mysize) + if (st._Mylast == st._Myfirst) _Tidy(); else - _Reallocate<_Reallocation_policy::_Exactly>(_Mysize); + _Reallocate<_Reallocation_policy::_Exactly>(size()); } } + void reserve(size_type new_cap) { if (this->capacity() < new_cap) _Reallocate<_Reallocation_policy::_Exactly>(new_cap); } + template void resize_and_overwrite(const size_type _New_size, _Operation _Op) { _Reallocate<_Reallocation_policy::_Exactly>(_New_size); - _Eos(std::move(_Op)(_Myfirst, _New_size)); + auto& st = _Mypair.second(); + // _Op writes up to _New_size and returns new size + size_type written = std::move(_Op)(st._Myfirst, _New_size); + _Eos(written); } #pragma endregion + void resize(size_type new_size, const_reference val) { auto old_size = this->size(); @@ -309,9 +378,10 @@ class basic_string { { resize(new_size); if (old_size < new_size) - std::fill_n(_Myfirst + old_size, new_size - old_size, val); + std::fill_n(_Mypair.second()._Myfirst + old_size, new_size - old_size, val); } } + void expand(size_type count, const_reference val) { if (count) @@ -319,29 +389,35 @@ class basic_string { auto old_size = this->size(); expand(count); if (count) - std::fill_n(_Myfirst + old_size, count, val); + std::fill_n(_Mypair.second()._Myfirst + old_size, count, val); } } + template pointer detach_abi(_Intty& len) YASIO__NOEXCEPT { - len = static_cast<_Intty>(this->size()); - auto ptr = _Myfirst; - _Myfirst = nullptr; - _Mysize = _Myres = 0; + auto& st = _Mypair.second(); + len = static_cast<_Intty>(this->size()); + auto ptr = st._Myfirst; + st._Myfirst = st._Mylast = st._Myend = nullptr; return ptr; } + pointer detach_abi() YASIO__NOEXCEPT { size_type ignored_len; return this->detach_abi(ignored_len); } + void attach_abi(pointer ptr, size_type len) { _Tidy(); - _Myfirst = ptr; - _Mysize = _Myres = len; + auto& st = _Mypair.second(); + st._Myfirst = ptr; + st._Mylast = ptr + len; + st._Myend = ptr + len; } + pointer release_pointer() YASIO__NOEXCEPT { return detach_abi(); } #pragma region find stubs, String operations @@ -367,41 +443,40 @@ class basic_string { my_type& replace(const size_type _Off, size_type _Nx, view_type value) { return this->replace(_Off, _Nx, value.data(), value.length()); } my_type& replace(const size_type _Off, size_type _Nx, const _Elem* const _Ptr, const size_type _Count) { // replace port from https://github.com/microsoft/stl - _YASIO_VERIFY_RANGE(_Off < _Mysize, "basic_string: out of range!"); - _Nx = (std::min)(_Nx, _Mysize - _Off); + auto& st = _Mypair.second(); + _YASIO_VERIFY_RANGE(_Off < size(), "basic_string: out of range!"); + _Nx = (std::min)(_Nx, size() - _Off); if (_Nx == _Count) { // size doesn't change, so a single move does the trick - _Traits::move(_Myfirst + _Off, _Ptr, _Count); + _Traits::move(st._Myfirst + _Off, _Ptr, _Count); return *this; } - const size_type _Old_size = _Mysize; + const size_type _Old_size = size(); const size_type _Suffix_size = _Old_size - _Nx - _Off + 1; if (_Count < _Nx) { // suffix shifts backwards; we don't have to move anything out of the way - _Elem* const _Old_ptr = _Myfirst; + _Elem* const _Old_ptr = st._Myfirst; _Elem* const _Insert_at = _Old_ptr + _Off; _Traits::move(_Insert_at, _Ptr, _Count); _Traits::move(_Insert_at + _Count, _Insert_at + _Nx, _Suffix_size); const auto _New_size = _Old_size - (_Nx - _Count); - // _ASAN_STRING_MODIFY(*this, _Old_size, _New_size); - _Mysize = _New_size; + _Eos(_New_size); return *this; } const size_type _Growth = static_cast(_Count - _Nx); - // checking for overlapping ranges is technically UB (considering string literals), so just always reallocate - // and copy to the new buffer if constant evaluated #if YASIO__HAS_CXX20 if (!std::is_constant_evaluated()) -#endif // _HAS_CXX20 +#endif { - if (_Growth <= _Myres - _Old_size) + const size_type _Old_capacity = capacity(); + if (_Growth <= _Old_capacity - _Old_size) { // growth fits - _Mysize = _Old_size + _Growth; - _Elem* const _Old_ptr = _Myfirst; + _Eos(_Old_size + _Growth); + _Elem* const _Old_ptr = st._Myfirst; _Elem* const _Insert_at = _Old_ptr + _Off; _Elem* const _Suffix_at = _Insert_at + _Nx; @@ -420,12 +495,7 @@ class basic_string { } _Traits::move(_Suffix_at + _Growth, _Suffix_at, _Suffix_size); - // next case must be move, in case _Ptr begins before _Insert_at and contains part of the hole; - // this case doesn't occur in insert because the new content must come from outside the removed - // content there (because in insert there is no removed content) _Traits::move(_Insert_at, _Ptr, _Ptr_shifted_after); - // the next case can be copy, because it comes from the chunk moved out of the way in the - // first move, and the hole we're filling can't alias the chunk we moved out of the way _Traits::copy(_Insert_at + _Ptr_shifted_after, _Ptr + _Growth + _Ptr_shifted_after, _Count - _Ptr_shifted_after); return *this; } @@ -439,24 +509,26 @@ class basic_string { }, _Off, _Nx, _Ptr, _Count); } + template my_type& _Reallocate_grow_by(const size_type _Size_increase, _Fty _Fn, _ArgTys... _Args) { - const size_type _Old_size = _Mysize; + const size_type _Old_size = size(); if (max_size() - _Old_size < _Size_increase) throw std::length_error("string too long"); const size_type _New_size = _Old_size + _Size_increase; - const size_type _Old_capacity = _Myres; const size_type _New_capacity = _Calculate_growth(_New_size); - pointer _New_ptr = _Alloc::reallocate(_Myfirst, _Myres, _New_capacity + 1); // throws + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + pointer _New_ptr = alloc.reallocate(st._Myfirst, static_cast(st._Myend - st._Myfirst), _New_capacity + 1); // throws - _Mysize = _New_size; - _Myres = _New_capacity; + _Eos(_New_size); + st._Myend = _New_ptr + (_New_capacity + 1); - const pointer _Old_ptr = _Myfirst; + const pointer _Old_ptr = st._Myfirst; _Fn(_New_ptr, _Old_size, _Args...); - _Myfirst = _New_ptr; + st._Myfirst = _New_ptr; return *this; } @@ -497,63 +569,86 @@ class basic_string { private: void _Eos(size_type size) YASIO__NOEXCEPT { - _Mysize = size; - _Myfirst[_Mysize] = static_cast(0); + auto& st = _Mypair.second(); + st._Mylast = st._Myfirst + size; + if (st._Myfirst) + *st._Mylast = static_cast(0); + } + + // convenience: get address of current null-terminator within capacity + pointer _Mylast_ptr() YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + return st._Mylast; // points to null terminator slot } + template ::value, int> = 0> void _Assign_range(_Iter first, _Iter last) { + auto& st = _Mypair.second(); auto ifirst = std::addressof(*first); static_assert(sizeof(*ifirst) == sizeof(value_type), "basic_string: iterator type incompatible!"); - if (ifirst != _Myfirst) + if (ifirst != st._Myfirst) { - _Mysize = 0; + st._Mylast = st._Myfirst; // size = 0 if (last > first) { const auto count = static_cast(std::distance(first, last)); resize(count); - std::copy_n((iterator)ifirst, count, _Myfirst); + std::copy_n((iterator)ifirst, count, st._Myfirst); } } } + void _Assign_rv(basic_string&& rhs) { - memcpy(this, &rhs, sizeof(rhs)); - memset(&rhs, 0, sizeof(rhs)); + // move allocator and storage + _Mypair.first() = std::move(rhs._Mypair.first()); + _Mypair.second() = rhs._Mypair.second(); + rhs._Mypair.second() = _Str_storage{}; } + enum class _Reallocation_policy { _At_least, _Exactly }; + template <_Reallocation_policy _Policy> void _Resize_reallocate(size_type size) { _Reallocate<_Policy>(size); _Eos(size); } + template <_Reallocation_policy _Policy> void _Reallocate(size_type size) { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); size_type new_cap; if YASIO__CONSTEXPR (_Policy == _Reallocation_policy::_Exactly) new_cap = size + 1; else - new_cap = (std::max)(_Calculate_growth(size), size + 1); - auto _Newvec = _Alloc::reallocate(_Myfirst, _Myres, new_cap); + new_cap = (std::max)(_Calculate_growth(size), size) + 1; + + pointer _Newvec = alloc.reallocate(st._Myfirst, static_cast(st._Myend - st._Myfirst), new_cap); if (_Newvec) { - _Myfirst = _Newvec; - _Myres = new_cap; + st._Myend = _Newvec + new_cap; + const auto cur_size = size; // caller will set exact size via _Eos + st._Myfirst = _Newvec; + st._Mylast = _Newvec + cur_size; } else throw std::bad_alloc{}; } + size_type _Calculate_growth(const size_type _Newsize) const { // given _Oldcapacity and _Newsize, calculate geometric growth const size_type _Oldcapacity = capacity(); - YASIO__CONSTEXPR auto _Max = max_size(); + const size_type _Max = max_size(); if (_Oldcapacity > _Max - _Oldcapacity / 2) return _Max; // geometric growth would overflow @@ -565,20 +660,20 @@ class basic_string { return _Geometric; // geometric growth is sufficient } + void _Tidy() YASIO__NOEXCEPT { // free all storage - if (_Myfirst) + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + if (st._Myfirst) { - _Alloc::deallocate(_Myfirst, _Myres); - _Myfirst = nullptr; - _Mysize = _Myres = 0; + alloc.deallocate(st._Myfirst, static_cast(st._Myend - st._Myfirst)); + st._Myfirst = st._Mylast = st._Myend = nullptr; } } - - pointer _Myfirst = nullptr; - size_type _Mysize = 0; - size_type _Myres = 0; }; + +// aliases using string = basic_string; #if defined(__cpp_lib_char8_t) using u8string = basic_string; diff --git a/yasio/vector.hpp b/yasio/vector.hpp new file mode 100644 index 00000000..4e17f813 --- /dev/null +++ b/yasio/vector.hpp @@ -0,0 +1,793 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// A multi-platform support c++11 library with focus on asynchronous socket I/O for any +// client application. +////////////////////////////////////////////////////////////////////////////////////////// +/* +The MIT License (MIT) + +Copyright (c) 2012-2025 HALX99 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Version: 5.0.0 + +The vector aka array_buffer concepts: + a. The memory model is similar to std::vector + b. The resize behavior different from STL, can allocate exactly + c. By default resize without fill (uninitialized for overwrite on POD path), + use insert/append instead if you want fill memory inside container + d. Support release internal buffer ownership with `release_pointer` + e. Transparent iterator + f. expand/append/insert/push_back will trigger memory allocate growth strategy + g. resize_and_overwrite (c++23) +*/ +#ifndef YASIO__VECTOR_HPP +#define YASIO__VECTOR_HPP + +#include +#include +#include +#include +#include +#include "yasio/type_traits.hpp" +#include "yasio/compiler/feature_test.hpp" +#include "yasio/memory.hpp" // for yasio::compressed_pair and ::yasio::construct_at + +namespace yasio +{ +template > +class vector { +public: + using value_type = _Ty; + using allocator_type = _Alloc; + using _Alloc_traits = std::allocator_traits<_Alloc>; + using pointer = typename _Alloc_traits::pointer; + using const_pointer = typename _Alloc_traits::const_pointer; + using reference = value_type&; + using const_reference = const value_type&; + using size_type = typename _Alloc_traits::size_type; + using iterator = pointer; + using const_iterator = const_pointer; + + // storage holder with three pointers: begin, last, end-cap + struct _Vec_storage { + pointer _Myfirst = nullptr; // begin + pointer _Mylast = nullptr; // one past last element + pointer _Myend = nullptr; // one past end of storage + }; + + vector() {} + explicit vector(const allocator_type& alloc) : _Mypair(alloc, _Vec_storage{}) {} + explicit vector(size_type count) { resize(static_cast(count)); } + vector(size_type count, const_reference val) { resize(static_cast(count), val); } + + template ::value, int> = 0> + vector(_Iter first, _Iter last) + { + assign(first, last); + } + + vector(const vector& rhs) { assign(rhs); } + vector(vector&& rhs) YASIO__NOEXCEPT { _Assign_rv(std::move(rhs)); } + + ~vector() { _Tidy(); } + + vector& operator=(const vector& rhs) + { + assign(rhs); + return *this; + } + vector& operator=(vector&& rhs) YASIO__NOEXCEPT + { + this->swap(rhs); + return *this; + } + + template + vector& operator+=(const _Cont& rhs) + { + return this->append(std::begin(rhs), std::end(rhs)); + } + + vector& operator+=(const_reference rhs) + { + this->push_back(rhs); + return *this; + } + + template ::value, int> = 0> + void assign(_Iter first, _Iter last) + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + + // if source is our own buffer, avoid self-assignment corruption + auto ifirst = std::addressof(*first); + static_assert(sizeof(*ifirst) == sizeof(value_type), "vector: iterator type incompatible!"); + if (ifirst != st._Myfirst) + { + clear(); + const auto count = static_cast(std::distance(first, last)); + reserve(count); + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + std::copy_n((iterator)ifirst, count, st._Myfirst); + else + std::uninitialized_copy((iterator)ifirst, (iterator)ifirst + count, st._Myfirst); + st._Mylast = st._Myfirst + count; + } + } + + void assign(const vector& rhs) { assign(rhs.begin(), rhs.end()); } + + void assign(vector&& rhs) { _Assign_rv(std::move(rhs)); } + + void swap(vector& rhs) YASIO__NOEXCEPT + { + auto& a = _Mypair.second(); + auto& b = rhs._Mypair.second(); + std::swap(a._Myfirst, b._Myfirst); + std::swap(a._Mylast, b._Mylast); + std::swap(a._Myend, b._Myend); + } + + // insert range by iterator + template ::value, int> = 0> + iterator insert(iterator pos, _Iter first, _Iter last) + { + auto& st = _Mypair.second(); + _YASIO_VERIFY_RANGE(pos >= st._Myfirst && pos <= st._Mylast && first <= last, "vector: out of range!"); + if (first == last) + return pos; + + auto insertion_off = static_cast(std::distance(st._Myfirst, pos)); + if (pos == st._Mylast) + { + append(first, last); + } + else + { + auto ifirst = std::addressof(*first); + static_assert(sizeof(*ifirst) == sizeof(value_type), "vector: iterator type incompatible!"); + auto count = static_cast(std::distance(first, last)); + + const auto old_size = size(); + expand(count); // may reallocate, updates _Mylast/size + // refresh pointers after possible reallocate + pos = _Mypair.second()._Myfirst + insertion_off; + auto mlast = _Mypair.second()._Mylast; + + auto move_to = pos + count; + auto tail_count = static_cast(mlast - move_to); + + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + { + // POD path: shift tail right into gap + std::copy_n(pos, tail_count, move_to); + // fill gap with incoming values + std::copy_n((iterator)ifirst, count, pos); + } + else + { + // non-POD path: + // 1) construct tail into new uninitialized slots + std::uninitialized_move(pos, mlast - count, move_to); + // 2) destroy original moved tail + auto& alloc = _Mypair.first(); + for (iterator it = pos; it != mlast - count; ++it) + _Alloc_traits::destroy(alloc, it); + // 3) construct inserted elements into the gap + std::uninitialized_copy((iterator)ifirst, (iterator)ifirst + count, pos); + } + // size already increased by expand(count) + } + return _Mypair.second()._Myfirst + insertion_off; + } + + // insert count copies of val + iterator insert(iterator pos, size_type count, const_reference val) + { + auto& st = _Mypair.second(); + _YASIO_VERIFY_RANGE(pos >= st._Myfirst && pos <= st._Mylast, "vector: out of range!"); + if (!count) + return pos; + + auto insertion_off = static_cast(std::distance(st._Myfirst, pos)); + if (pos == st._Mylast) + { + append(count, val); + } + else + { + const auto old_size = size(); + expand(count); // may reallocate + pos = _Mypair.second()._Myfirst + insertion_off; + auto mlast = _Mypair.second()._Mylast; + auto move_to = pos + count; + auto tail_count = static_cast(mlast - move_to); + + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + { + std::copy_n(pos, tail_count, move_to); + std::fill_n(pos, count, val); + } + else + { + // move tail into new uninitialized slots + std::uninitialized_move(pos, mlast - count, move_to); + // destroy original moved tail + auto& alloc = _Mypair.first(); + for (iterator it = pos; it != mlast - count; ++it) + _Alloc_traits::destroy(alloc, it); + // construct 'count' elements in gap + std::uninitialized_fill_n(pos, count, val); + } + // size already increased by expand(count) + } + return _Mypair.second()._Myfirst + insertion_off; + } + + // single-element insert + iterator insert(iterator pos, const value_type& val) { return emplace(pos, val); } + iterator insert(iterator pos, value_type&& val) { return emplace(pos, std::move(val)); } + + // emplace at position + template + iterator emplace(iterator pos, _Valty&&... val) + { + auto& st = _Mypair.second(); + auto insertion_off = static_cast(std::distance(st._Myfirst, pos)); + _YASIO_VERIFY_RANGE(insertion_off <= size(), "vector: out of range!"); +#if YASIO__HAS_CXX20 + emplace_back(std::forward<_Valty>(val)...); + std::rotate(begin() + insertion_off, end() - 1, end()); + return (begin() + insertion_off); +#else + // expand by one, shift tail, and construct at pos + if (pos == st._Mylast) + { + emplace_back(std::forward<_Valty>(val)...); + } + else + { + expand(1); + // refresh pos after expand + pos = _Mypair.second()._Myfirst + insertion_off; + auto mlast = _Mypair.second()._Mylast; + + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + { + std::copy_n(pos, static_cast(mlast - (pos + 1)), pos + 1); + ::yasio::construct_at(pos, std::forward<_Valty>(val)...); + } + else + { + // non-POD: construct tail into new slot + std::uninitialized_move(pos, mlast - 1, pos + 1); + // destroy original moved tail + auto& alloc = _Mypair.first(); + for (iterator it = pos; it != mlast - 1; ++it) + _Alloc_traits::destroy(alloc, it); + // construct the new element at pos + _Alloc_traits::construct(_Mypair.first(), pos, std::forward<_Valty>(val)...); + } + } + return _Mypair.second()._Myfirst + insertion_off; +#endif + } + + // append range + template ::value, int> = 0> + vector& append(_Iter first, const _Iter last) + { + if (first == last) + return *this; + + auto ifirst = std::addressof(*first); + static_assert(sizeof(*ifirst) == sizeof(value_type), "vector: iterator type incompatible!"); + auto count = static_cast(std::distance(first, last)); + const auto old_size = size(); + expand(count); + + auto dst = _Mypair.second()._Myfirst + old_size; + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + std::copy_n((iterator)ifirst, count, dst); + else + std::uninitialized_copy((iterator)ifirst, (iterator)ifirst + count, dst); + return *this; + } + + // append count copies + vector& append(size_type count, const_reference val) + { + if (!count) + return *this; + const auto old_size = size(); + expand(count); + auto dst = _Mypair.second()._Myfirst + old_size; + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + std::fill_n(dst, count, val); + else + std::uninitialized_fill_n(dst, count, val); + return *this; + } + + // push_back + void push_back(value_type&& val) { push_back(val); } + void push_back(const value_type& val) { emplace_back(val); } + + // pop_back + void pop_back() + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + if (!empty()) + { + if YASIO__CONSTEXPR (!std::is_trivially_destructible_v) + _Alloc_traits::destroy(alloc, st._Mylast - 1); + st._Mylast = st._Mylast - 1; + } + } + + // emplace_back + template + inline value_type& emplace_back(_Valty&&... val) + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + if (st._Mylast != st._Myend) + { + _Alloc_traits::construct(alloc, st._Mylast, std::forward<_Valty>(val)...); + return *st._Mylast++; + } + return *_Emplace_back_reallocate(std::forward<_Valty>(val)...); + } + + // erase single element by const_iterator + iterator erase(const_iterator pos) { return erase(const_cast(pos)); } + + // erase range by const_iterator + iterator erase(const_iterator first, const_iterator last) { return erase(const_cast(first), const_cast(last)); } + + // erase single + iterator erase(iterator pos) + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + _YASIO_VERIFY_RANGE(pos >= st._Myfirst && pos < st._Mylast, "vector: out of range!"); + iterator next = pos + 1; + + if YASIO__CONSTEXPR (!std::is_trivially_destructible_v) + { + _Alloc_traits::destroy(alloc, pos); + std::move(next, st._Mylast, pos); + --st._Mylast; + _Alloc_traits::destroy(alloc, st._Mylast); + } + else + { + std::move(next, st._Mylast, pos); + --st._Mylast; + } + return pos; + } + + // erase range + iterator erase(iterator first, iterator last) + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + _YASIO_VERIFY_RANGE((first <= last) && first >= st._Myfirst && last <= st._Mylast, "vector: out of range!"); + size_type count = static_cast(last - first); + + if YASIO__CONSTEXPR (!std::is_trivially_destructible_v) + { + for (iterator it = first; it != last; ++it) + _Alloc_traits::destroy(alloc, it); + + iterator new_end = std::move(last, st._Mylast, first); + for (iterator it = new_end; it != st._Mylast; ++it) + _Alloc_traits::destroy(alloc, it); + } + else + { + std::move(last, st._Mylast, first); + } + st._Mylast -= count; + return first; + } + + // front/back + value_type& front() + { + _YASIO_VERIFY_RANGE(!empty(), "vector: out of range!"); + return *_Mypair.second()._Myfirst; + } + const value_type& front() const + { + _YASIO_VERIFY_RANGE(!empty(), "vector: out of range!"); + return *_Mypair.second()._Myfirst; + } + value_type& back() + { + _YASIO_VERIFY_RANGE(!empty(), "vector: out of range!"); + return *(_Mypair.second()._Mylast - 1); + } + const value_type& back() const + { + _YASIO_VERIFY_RANGE(!empty(), "vector: out of range!"); + return *(_Mypair.second()._Mylast - 1); + } + + // iterators and data + YASIO__CONSTEXPR size_type max_size() YASIO__NOEXCEPT { return _Alloc_traits::max_size(_Mypair.first()); } + iterator begin() YASIO__NOEXCEPT { return _Mypair.second()._Myfirst; } + iterator end() YASIO__NOEXCEPT { return _Mypair.second()._Mylast; } + const_iterator begin() const YASIO__NOEXCEPT { return _Mypair.second()._Myfirst; } + const_iterator end() const YASIO__NOEXCEPT { return _Mypair.second()._Mylast; } + pointer data() YASIO__NOEXCEPT { return _Mypair.second()._Myfirst; } + const_pointer data() const YASIO__NOEXCEPT { return _Mypair.second()._Myfirst; } + size_type capacity() const YASIO__NOEXCEPT { return static_cast(_Mypair.second()._Myend - _Mypair.second()._Myfirst); } + size_type size() const YASIO__NOEXCEPT { return static_cast(_Mypair.second()._Mylast - _Mypair.second()._Myfirst); } + size_type length() const YASIO__NOEXCEPT { return size(); } + + // clear + void clear() YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + if YASIO__CONSTEXPR (!std::is_trivially_destructible_v) + { + auto& alloc = _Mypair.first(); + for (pointer p = st._Myfirst; p != st._Mylast; ++p) + _Alloc_traits::destroy(alloc, p); + } + st._Mylast = st._Myfirst; + } + + bool empty() const YASIO__NOEXCEPT { return _Mypair.second()._Mylast == _Mypair.second()._Myfirst; } + + // operator[] and at + const_reference operator[](size_type index) const { return this->at(index); } + reference operator[](size_type index) { return this->at(index); } + + const_reference at(size_type index) const + { + _YASIO_VERIFY_RANGE(index < this->size(), "vector: out of range!"); + return _Mypair.second()._Myfirst[index]; + } + reference at(size_type index) + { + _YASIO_VERIFY_RANGE(index < this->size(), "vector: out of range!"); + return _Mypair.second()._Myfirst[index]; + } + +#pragma region modify size and capacity + // resize without fill + void resize(size_type new_size) + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + const auto old_size = size(); + if (capacity() < new_size) + _Resize_reallocate<_Reallocation_policy::_Exactly>(new_size); + + if (new_size < old_size) + { + if YASIO__CONSTEXPR (!std::is_trivially_destructible_v) + { + for (pointer p = st._Myfirst + new_size; p != st._Mylast; ++p) + _Alloc_traits::destroy(alloc, p); + } + st._Mylast = st._Myfirst + new_size; + } + else if (new_size > old_size) + { + if YASIO__CONSTEXPR (std::is_trivially_default_constructible_v) + { + // POD path: leave as uninitialized for overwrite + st._Mylast = st._Myfirst + new_size; + } + else + { + std::uninitialized_default_construct(st._Myfirst + old_size, st._Myfirst + new_size); + st._Mylast = st._Myfirst + new_size; + } + } + } + + // expand by count without fill + void expand(size_type count) + { + auto& st = _Mypair.second(); + const auto new_size = this->size() + count; + if (this->capacity() < new_size) + _Resize_reallocate<_Reallocation_policy::_At_least>(new_size); + // initialize newly added region for non-POD + if YASIO__CONSTEXPR (std::is_trivially_default_constructible_v) + { + st._Mylast = st._Myfirst + new_size; + } + else + { + std::uninitialized_default_construct(st._Mylast, st._Myfirst + new_size); + st._Mylast = st._Myfirst + new_size; + } + } + + // shrink_to_fit + void shrink_to_fit() + { // reduce capacity to size, provide strong guarantee + auto& st = _Mypair.second(); + if (st._Mylast != st._Myend) + { + if (st._Mylast == st._Myfirst) + _Tidy(); + else + _Reallocate<_Reallocation_policy::_Exactly>(size()); + } + } + + // reserve capacity + void reserve(size_type new_cap) + { + if (this->capacity() < new_cap) + _Reallocate<_Reallocation_policy::_Exactly>(new_cap); + } + + // resize_and_overwrite (c++23-like) + template + void resize_and_overwrite(const size_type new_size, _Operation op) + { + _Reallocate<_Reallocation_policy::_Exactly>(new_size); + auto& st = _Mypair.second(); + st._Mylast = st._Myfirst + std::move(op)(st._Myfirst, new_size); + } +#pragma endregion + + // resize with fill + void resize(size_type new_size, const_reference val) + { + auto& st = _Mypair.second(); + const auto old_size = this->size(); + if (old_size == new_size) + return; + + resize(new_size); + if (old_size < new_size) + { + auto dst = st._Myfirst + old_size; + auto fill_count = new_size - old_size; + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + std::fill_n(dst, fill_count, val); + else + std::uninitialized_fill_n(dst, fill_count, val); + } + } + + // expand with fill + void expand(size_type count, const_reference val) + { + if (!count) + return; + auto& st = _Mypair.second(); + const auto old_size = this->size(); + expand(count); + auto dst = st._Myfirst + old_size; + if YASIO__CONSTEXPR (std::is_trivially_copy_constructible_v) + std::fill_n(dst, count, val); + else + std::uninitialized_fill_n(dst, count, val); + } + + // helpers + ptrdiff_t index_of(const_reference val) const YASIO__NOEXCEPT + { + auto it = std::find(begin(), end(), val); + if (it != this->end()) + return std::distance(begin(), it); + return -1; + } + + void reset(size_type new_size) + { + resize(new_size); + std::memset(_Mypair.second()._Myfirst, 0x0, size_bytes()); + } + + size_t size_bytes() const YASIO__NOEXCEPT { return static_cast(this->size()) * sizeof(value_type); } + + template + pointer detach_abi(_Intty& len) YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + len = static_cast<_Intty>(this->size()); + auto ptr = st._Myfirst; + st._Myfirst = st._Mylast = st._Myend = nullptr; + return ptr; + } + + pointer detach_abi() YASIO__NOEXCEPT + { + size_type ignored_len; + return this->detach_abi(ignored_len); + } + + void attach_abi(pointer ptr, size_type len) + { + _Tidy(); + auto& st = _Mypair.second(); + st._Myfirst = ptr; + st._Mylast = ptr + len; + st._Myend = ptr + len; + } + + pointer release_pointer() YASIO__NOEXCEPT { return detach_abi(); } + +private: + enum class _Reallocation_policy + { + _At_least, + _Exactly + }; + + void _Eos(size_type new_size) YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + st._Mylast = st._Myfirst + new_size; + } + + // emplace_back with reallocate + template + pointer _Emplace_back_reallocate(_Valty&&... val) + { + auto& st = _Mypair.second(); + const auto old_size = size(); + + if (old_size == max_size()) + throw std::length_error("vector too long"); + + const size_type new_size = old_size + 1; + _Resize_reallocate<_Reallocation_policy::_At_least>(new_size); + auto& alloc = _Mypair.first(); + _Alloc_traits::construct(alloc, st._Myfirst + old_size, std::forward<_Valty>(val)...); + st._Mylast = st._Myfirst + new_size; + return st._Myfirst + old_size; + } + + // growth calculation + size_type _Calculate_growth(const size_type new_size) const + { + const size_type old_cap = capacity(); + YASIO__CONSTEXPR auto max_cap = (std::numeric_limits::max)(); + + if (old_cap > max_cap - old_cap / 2) + return max_cap; // geometric growth would overflow + + const size_type geometric = old_cap + (old_cap >> 1); + + if (geometric < new_size) + return new_size; // geometric growth would be insufficient + + return geometric; // geometric growth is sufficient + } + + // reallocate helpers + template <_Reallocation_policy _Policy> + void _Resize_reallocate(size_type size) + { + _Reallocate<_Policy>(size); + _Eos(size); + } + + template <_Reallocation_policy _Policy> + void _Reallocate(size_type size) + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + size_type new_cap; + if YASIO__CONSTEXPR (_Policy == _Reallocation_policy::_Exactly) + new_cap = size; + else + new_cap = _Calculate_growth(size); + + pointer newbuf = _Alloc_traits::allocate(alloc, new_cap); + // move-construct into new buffer + if YASIO__CONSTEXPR (std::is_trivially_move_constructible_v) + { + // POD path: copy bytes + std::uninitialized_copy(st._Myfirst, st._Mylast, newbuf); + } + else + { + std::uninitialized_move(st._Myfirst, st._Mylast, newbuf); + } + + // destroy old elements + if YASIO__CONSTEXPR (!std::is_trivially_destructible_v) + { + for (pointer p = st._Myfirst; p != st._Mylast; ++p) + _Alloc_traits::destroy(alloc, p); + } + // deallocate old storage + if (st._Myfirst) + _Alloc_traits::deallocate(alloc, st._Myfirst, static_cast(st._Myend - st._Myfirst)); + + // update storage pointers + const auto old_size = static_cast(st._Mylast - st._Myfirst); + st._Myfirst = newbuf; + st._Mylast = newbuf + old_size; + st._Myend = newbuf + new_cap; + } + + // tidy release + void _Tidy() YASIO__NOEXCEPT + { + auto& st = _Mypair.second(); + auto& alloc = _Mypair.first(); + if (st._Myfirst) + { + if YASIO__CONSTEXPR (!std::is_trivially_destructible_v) + { + for (pointer p = st._Myfirst; p != st._Mylast; ++p) + _Alloc_traits::destroy(alloc, p); + } + _Alloc_traits::deallocate(alloc, st._Myfirst, static_cast(st._Myend - st._Myfirst)); + st._Myfirst = st._Mylast = st._Myend = nullptr; + } + } + + void _Assign_rv(vector&& rhs) + { + // move allocator and storage pointers + _Mypair.first() = std::move(rhs._Mypair.first()); + _Mypair.second() = rhs._Mypair.second(); + rhs._Mypair.second() = _Vec_storage{}; + } + + ::yasio::compressed_pair _Mypair; +}; + +#pragma region c++20 like std::erase +template +void erase(vector<_Ty, _Alloc>& cont, const _Ty& val) +{ + cont.erase(std::remove(cont.begin(), cont.end(), val), cont.end()); +} +template +void erase_if(vector<_Ty, _Alloc>& cont, _Pr pred) +{ + cont.erase(std::remove_if(cont.begin(), cont.end(), pred), cont.end()); +} +#pragma endregion + +#pragma region ordered insert, for flat container emulating +template +inline typename _Cont::iterator ordered_insert(_Cont& vec, typename _Cont::value_type const& val) +{ + return vec.insert(std::upper_bound(vec.begin(), vec.end(), val), val); +} + +template +inline typename _Cont::iterator ordered_insert(_Cont& vec, typename _Cont::value_type const& val, _Pred pred) +{ + return vec.insert(std::upper_bound(vec.begin(), vec.end(), val, pred), val); +} +#pragma endregion + +} // namespace yasio +#endif diff --git a/yasio/yasio.natvis b/yasio/yasio.natvis index e49aba7d..f8274abb 100644 --- a/yasio/yasio.natvis +++ b/yasio/yasio.natvis @@ -1,82 +1,82 @@ - - - {{ size={_Mysize} capacity={_Myres} }} + + + {{ size={_Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst} capacity={_Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst} }} - _Mysize - _Myres + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst - _Mysize - _Myfirst + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myfirst - {{ size={_Mysize} {_Myfirst,[_Mysize]s8} }} + {{ size={_Mysize} {_Mypair._Myval2._Myfirst,[_Mysize]s8} }} - _Mysize - _Myres + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst - _Mysize - _Myfirst + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myfirst - {_Myfirst,s8} - {&_Myfirst,s8} - _Myfirst,s8 - &_Myfirst,s8 + {_Mypair._Myval2._Myfirst,s8} + {&_Mypair._Myval2._Myfirst,s8} + _Mypair._Myval2._Myfirst,s8 + &_Mypair._Myval2._Myfirst,s8 - _Mysize - _Myres - - _Mysize - _Myfirst + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst + + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myfirst - - _Mysize - &_Myfirst + + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + &_Mypair._Myval2._Myfirst - {_Myfirst,su} - {&_Myfirst,su} - _Myfirst,su - &_Myfirst,su + {_Mypair._Myval2._Myfirst,su} + {&_Mypair._Myval2._Myfirst,su} + _Mypair._Myval2._Myfirst,su + &_Mypair._Myval2._Myfirst,su - _Mysize - _Myres - - _Mysize - _Myfirst + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst + + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myfirst - - _Mysize - &_Myfirst + + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + &_Mypair._Myval2._Myfirst - {_Myfirst,s32} - {&_Myfirst,s32} - _Myfirst,s32 - &_Myfirst,s32 + {_Mypair._Myval2._Myfirst,s32} + {&_Mypair._Myval2._Myfirst,s32} + _Mypair._Myval2._Myfirst,s32 + &_Mypair._Myval2._Myfirst,s32 - _Mysize - _Myres - - _Mysize - _Myfirst + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst + + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + _Mypair._Myval2._Myfirst - - _Mysize - &_Myfirst + + _Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst + &_Mypair._Myval2._Myfirst