////////////////////////////////////////////////////////////////////////////////////////// // 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-2023 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. The yasio dedicated string (API not 100% compatible with stl) concepts: a. no SSO, no COW, sizeof(yasio::string) = 24(x64), 12(x86) 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. The operations expand/append/insert/push_back/+= will trigger memory allocate growth strategy MSVC g. resize_and_overwrite (c++23) h. provide API replace_all, to_upper, to_lower which stl not provide i. More suitable for small text file (> SSO size) read API */ #pragma once #include #include #include #include #include #include #include "yasio/buffer_alloc.hpp" #include "yasio/string_view.hpp" namespace yasio { template , enable_if_t::value, int> = 0> class basic_string { public: using pointer = _Elem*; using const_pointer = const _Elem*; using reference = _Elem&; using const_reference = const _Elem&; using value_type = _Elem; using iterator = _Elem*; // transparent iterator using const_iterator = const _Elem*; using allocator_type = _Alloc; using _Alloc_traits = buffer_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; basic_string() {} basic_string(nullptr_t) = delete; explicit basic_string(size_type count) { resize(static_cast(count)); } basic_string(size_type count, const_reference val) { resize(static_cast(count), val); } template ::value, int> = 0> basic_string(_Iter first, _Iter last) { assign(first, last); } basic_string(const basic_string& rhs) { assign(rhs); }; basic_string(basic_string&& rhs) YASIO__NOEXCEPT { assign(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); return *this; } basic_string& operator=(basic_string&& rhs) YASIO__NOEXCEPT { this->swap(rhs); return *this; } template basic_string& operator+=(const _Cont& rhs) { return this->append(std::begin(rhs), std::end(rhs)); } basic_string& 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(view_type rhs) { _Assign_range(rhs.begin(), rhs.end()); } void assign(const_pointer ntcs) { this->assign(ntcs, static_cast(_Traits::length(ntcs))); } 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); } 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!"); if (first != last) { auto insertion_pos = static_cast(std::distance(_Myfirst, _Where)); if (_Where == _Mylast) append(first, last); else { 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 (insertion_pos >= 0) { auto old_size = _Mylast - _Myfirst; expand(count); _Where = _Myfirst + insertion_pos; _Mylast = _Myfirst + _Mysize; auto move_to = _Where + count; std::copy_n(_Where, _Mylast - move_to, move_to); std::copy_n((iterator)ifirst, count, _Where); } } return _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!"); if (count) { auto insertion_pos = std::distance(_Myfirst, _Where); if (_Where == _Mylast) append(count, val); else { if (insertion_pos >= 0) { const auto old_size = _Mysize; expand(count); _Where = _Myfirst + insertion_pos; _Mylast = _Myfirst + _Mysize; auto move_to = _Where + count; std::copy_n(_Where, _Mylast - move_to, move_to); std::fill_n(_Where, count, val); } } return _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 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; expand(count); std::copy_n((iterator)ifirst, count, _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); 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); return first; } value_type& front() { _YASIO_VERIFY_RANGE(!empty(), "basic_string: out of range!"); return *_Myfirst; } value_type& back() { _YASIO_VERIFY_RANGE(!empty(), "basic_string: out of range!"); return _Myfirst[_Mysize - 1]; } 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; } const_iterator begin() const YASIO__NOEXCEPT { return this->data(); } const_iterator end() const YASIO__NOEXCEPT { return begin() + _Mysize; } #pragma endregion pointer data() YASIO__NOEXCEPT { return _Myfirst ? _Myfirst : reinterpret_cast(&_Myfirst); } const_pointer data() const YASIO__NOEXCEPT { return _Myfirst ? _Myfirst : reinterpret_cast(&_Myfirst); } const_pointer c_str() const YASIO__NOEXCEPT { return this->data(); } 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]; } reference at(size_type index) { _YASIO_VERIFY_RANGE(index < this->size(), "basic_string: out of range!"); return _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; } 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); } } 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(); } #pragma region find stubs, String operations size_t find(value_type c, size_t pos = 0) const YASIO__NOEXCEPT { return view().find(c, pos); } size_t find(view_type str, size_t pos = 0) const YASIO__NOEXCEPT { return view().find(str, pos); } size_t rfind(value_type c, size_t pos = npos) const YASIO__NOEXCEPT { return view().rfind(c, pos); } size_t rfind(view_type str, size_t pos = npos) const YASIO__NOEXCEPT { return view().rfind(str, pos); } size_t find_first_of(value_type c, size_t pos = 0) const YASIO__NOEXCEPT { return view().find_first_of(c, pos); } size_t find_first_of(view_type str, size_t pos = 0) const YASIO__NOEXCEPT { return view().find_first_of(str, pos); } size_t find_last_of(value_type c, size_t pos = npos) const YASIO__NOEXCEPT { return view().find_last_of(c, pos); } size_t find_last_of(view_type str, size_t pos = npos) const YASIO__NOEXCEPT { return view().find_last_of(str, pos); } size_t find_first_not_of(value_type c, size_t pos = 0) const YASIO__NOEXCEPT { return view().find_first_not_of(c, pos); } size_t find_first_not_of(view_type str, size_t pos = 0) const YASIO__NOEXCEPT { return view().find_first_not_of(str, pos); } int compare(view_type str) const YASIO__NOEXCEPT { return view().compare(str); } my_type substr(size_t pos = 0, size_t len = npos) const { return my_type{view().substr(pos, len)}; } 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); if (_Nx == _Count) { // size doesn't change, so a single move does the trick _Traits::move(_Myfirst + _Off, _Ptr, _Count); return *this; } const size_type _Old_size = _Mysize; 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 _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; 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 { if (_Growth <= _Myres - _Old_size) { // growth fits _Mysize = _Old_size + _Growth; _Elem* const _Old_ptr = _Myfirst; _Elem* const _Insert_at = _Old_ptr + _Off; _Elem* const _Suffix_at = _Insert_at + _Nx; size_type _Ptr_shifted_after; // see rationale in insert if (_Ptr + _Count <= _Insert_at || _Ptr > _Old_ptr + _Old_size) { _Ptr_shifted_after = _Count; } else if (_Suffix_at <= _Ptr) { _Ptr_shifted_after = 0; } else { _Ptr_shifted_after = static_cast(_Suffix_at - _Ptr); } _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; } } return _Reallocate_grow_by( _Growth, [](_Elem* const _New_ptr, const size_type _Old_size, const size_type _Off, const size_type _Nx, const _Elem* const _Ptr, const size_type _Count) { _Traits::copy(_New_ptr + _Off + _Count, _New_ptr + _Off + _Nx, _Old_size - _Nx - _Off + 1); _Traits::copy(_New_ptr + _Off, _Ptr, _Count); }, _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; 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 _Mysize = _New_size; _Myres = _New_capacity; const pointer _Old_ptr = _Myfirst; _Fn(_New_ptr, _Old_size, _Args...); _Myfirst = _New_ptr; return *this; } #pragma endregion #pragma region replace all stubs, yasio string spec size_t replace_all(view_type from, view_type to) { if (from == to) return 0; int hints = 0; size_t pos = 0; const size_t predicate = !from.empty() ? 0 : 1; while ((pos = this->find(from, pos)) != my_type::npos) { (void)this->replace(pos, from.length(), to); pos += (to.length() + predicate); ++hints; } return hints; } void replace_all(value_type from, value_type to) YASIO__NOEXCEPT { std::replace(this->begin(), this->end(), from, to); } #pragma endregion #pragma region to_lower, to_upper my_type& to_lower() YASIO__NOEXCEPT { std::transform(this->begin(), this->end(), this->begin(), ::tolower); return *this; } my_type& to_upper() YASIO__NOEXCEPT { std::transform(this->begin(), this->end(), this->begin(), ::toupper); return *this; } #pragma endregion private: void _Eos(size_type size) YASIO__NOEXCEPT { _Mysize = size; _Myfirst[_Mysize] = static_cast(0); } template ::value, int> = 0> void _Assign_range(_Iter first, _Iter last) { auto ifirst = std::addressof(*first); static_assert(sizeof(*ifirst) == sizeof(value_type), "basic_string: 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(basic_string&& rhs) { memcpy(this, &rhs, sizeof(rhs)); memset(&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 + 1; else new_cap = (std::max)(_Calculate_growth(size), size + 1); 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 _Newsize) const { // given _Oldcapacity and _Newsize, calculate geometric growth const size_type _Oldcapacity = capacity(); YASIO__CONSTEXPR auto _Max = max_size(); if (_Oldcapacity > _Max - _Oldcapacity / 2) return _Max; // geometric growth would overflow const size_type _Geometric = _Oldcapacity + (_Oldcapacity >> 1); if (_Geometric < _Newsize) return _Newsize; // 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; }; using string = basic_string; #if defined(__cpp_lib_char8_t) using u8string = basic_string; #endif using wstring = basic_string; using u16string = basic_string; using u32string = basic_string; } // namespace yasio