/** This file is a part of rexy's general purpose library Copyright (C) 2020-2022 rexy712 This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef REXY_STRING_BASE_HPP #define REXY_STRING_BASE_HPP #include //is_same, integral_contant, enable_if, etc #include //forward #include //size_t,ptrdiff #include //strlen #include //CHAR_BIT #include //reverse_iterator #include //ostream #include "steal.hpp" #include "utility.hpp" #include "traits.hpp" #include "expression.hpp" #include "detail/string_appender.hpp" #include "detail/hasallocator.hpp" #include "rexy.hpp" #include "compat/constexpr.hpp" #if __cplusplus >= 202002L #include #endif namespace rexy{ template class basic_string_view; //Base of all RAII strings. Its use is allowing passing of rexy strings to functions without knowing the exact type template class string_base { public: using value_type = Char; using size_type = size_t; using difference_type = ptrdiff_t; using pointer = value_type*; using const_pointer = const value_type*; using reference = value_type&; using const_reference = const value_type&; using iterator = pointer; using const_iterator = const_pointer; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; private: static constexpr size_type EXTRA_SDATA_LEN = 0; //represent long string struct ldata{ unsigned char islong:1; //common subsequence with short string size_type capacity:(CHAR_BIT*sizeof(size_type)-1); //take away last bit from capacity for islong size_type length; //length of string excluding null terminator constexpr ldata(void)noexcept: islong(0), capacity(0), length(0){} }; static constexpr size_type MAX_SHORT_LEN = EXTRA_SDATA_LEN+sizeof(ldata)-2; //represent short string struct sdata{ unsigned char islong:1; //common subsequence with long string unsigned char length:(CHAR_BIT-1); //take away last bit from length for islong, excludes null terminator value_type data[MAX_SHORT_LEN+1]; //char array for string storage constexpr sdata(void)noexcept: islong(0), length(0), data{}{} }; //union of short and long string representations. Default to long representation for wstring_view's use case. union combine_data{ ldata l; sdata s; constexpr combine_data(void)noexcept: l(){} }m_data; //direct access to current string data regardless of representation. Increases access speed. pointer m_raw = m_data.s.data; protected: //Functions for handling long vs short string manipulation. Use this instead of directly modifying m_data. constexpr void set_islong_flag(bool b){ //although well defined to set either one at any time, constexpr functions cannot change active union member. if(b) m_data.l.islong = b; else m_data.s.islong = b; } constexpr bool islong(void)const{ //common standard layout union member subsequence, never undefined behavior return m_data.l.islong; } constexpr pointer set_short_ptr(void){ set_islong_flag(false); return m_raw = m_data.s.data; } constexpr pointer set_long_ptr(pointer ptr){ set_islong_flag(true); return m_raw = ptr; } constexpr pointer get_long_ptr(void){return m_raw;} constexpr pointer get_short_ptr(void){return m_raw;} constexpr const_pointer get_long_ptr(void)const{return m_raw;} constexpr const_pointer get_short_ptr(void)const{return m_raw;} constexpr pointer get_pointer(void){return m_raw;} constexpr const_pointer get_pointer(void)const{return m_raw;} constexpr void set_long_length(size_type len){m_data.l.length = len;} constexpr size_type get_long_length(void)const{return m_data.l.length;} constexpr void set_short_length(size_type len){m_data.s.length = static_cast(len);} constexpr size_type get_short_length(void)const{return m_data.s.length;} constexpr void set_long_capacity(size_type cap){m_data.l.capacity = cap;} constexpr void set_short_capacity(size_type){} constexpr size_type get_long_capacity(void)const{return m_data.l.capacity;} constexpr size_type get_short_capacity(void)const{return MAX_SHORT_LEN;} constexpr void set_length(size_type s){ if(islong()) set_long_length(s); else set_short_length(s); } protected: constexpr string_base(void)noexcept = default; //Initialize without copying constexpr string_base(pointer data, size_type len, size_type cap)noexcept{ if(cap > MAX_SHORT_LEN){ set_islong_flag(true); set_long_ptr(data); set_long_length(len); set_long_capacity(cap); }else if(len){ set_islong_flag(false); pointer raw = set_short_ptr(); if(len) memcpy(raw, data, sizeof(value_type)*len); raw[len] = 0; set_short_length(len); set_short_capacity(cap); } } constexpr string_base(pointer data, size_type len)noexcept: string_base(data, len, len){} //Copy ctor, copy length+capacity+short string, not long string value constexpr string_base(const string_base& s)noexcept: m_data(s.m_data){} constexpr string_base(string_base&& s)noexcept: m_data(std::move(s.m_data)), m_raw(s.islong() ? s.m_raw : m_data.s.data) { s.set_islong_flag(false); } REXY_CPP20_CONSTEXPR ~string_base(void)noexcept = default; constexpr string_base& operator=(string_base&& s)noexcept{ std::swap(m_data, s.m_data); if(this->islong()) std::swap(m_raw, s.m_raw); else{ s.m_raw = m_raw; m_raw = m_data.s.data; } return *this; } public: //Length of string not including null terminator constexpr size_type length(void)const noexcept{ if(islong()) return get_long_length(); else return get_short_length(); } constexpr size_type capacity(void)const noexcept{ if(islong()) return get_long_capacity(); else return get_short_capacity(); } //direct access to managed pointer constexpr pointer c_str(void)noexcept{return get_pointer();} constexpr const_pointer c_str(void)const noexcept{return get_pointer();} constexpr pointer get(void)noexcept{return get_pointer();} constexpr const_pointer get(void)const noexcept{return get_pointer();} constexpr operator pointer(void)noexcept{return get_pointer();} constexpr operator const_pointer(void)const noexcept{return get_pointer();} //true if m_data is not empty constexpr bool valid(void)const noexcept{return length() > 0;} constexpr reference operator[](size_type i)noexcept{return get_pointer()[i];} constexpr const_reference operator[](size_type i)const noexcept{return get_pointer()[i];} constexpr const_iterator search(const string_base& s)const; constexpr const_iterator search(const_pointer c)const; constexpr iterator search(const string_base& s); constexpr iterator search(const_pointer c); template constexpr const_iterator search(const string_base& s, const Searcher& searcher)const; template constexpr const_iterator search(const_pointer c, const Searcher& searcher)const; template constexpr iterator search(const string_base& s, const Searcher& searcher); template constexpr iterator search(const_pointer c, const Searcher& searcher); constexpr bool compare(const string_base& s)const{return *this == s;} constexpr bool compare(const_pointer c)const{return *this == c;} constexpr iterator begin(void){return get_pointer();} constexpr const_iterator begin(void)const{return get_pointer();} constexpr iterator end(void){return get_pointer()+length();} constexpr const_iterator end(void)const{return get_pointer()+length();} constexpr const_iterator cbegin(void)const{return begin();} constexpr const_iterator cend(void)const{return end();} constexpr reverse_iterator rbegin(void){return reverse_iterator(get_pointer()+length());} constexpr const_reverse_iterator rbegin(void)const{return const_reverse_iterator(get_pointer()+length());} constexpr reverse_iterator rend(void){return reverse_iterator(get_pointer()-1);} constexpr const_reverse_iterator rend(void)const{return const_reverse_iterator(get_pointer()-1);} constexpr const_reverse_iterator crbegin(void)const{return rbegin();} constexpr const_reverse_iterator crend(void)const{return rend();} static constexpr bool uses_sso(void){return true;} static constexpr size_type short_string_size(void){return MAX_SHORT_LEN;} }; //Supplies all functions that string_base can't implement template class basic_string : protected detail::hasallocator, public string_base { public: using value_type = typename string_base::value_type; using size_type = typename string_base::size_type; using difference_type = typename string_base::difference_type; using pointer = typename string_base::pointer; using const_pointer = typename string_base::const_pointer; using reference = typename string_base::reference; using const_reference = typename string_base::const_reference; using iterator = typename string_base::iterator; using const_iterator = typename string_base::const_iterator; using reverse_iterator = typename string_base::reverse_iterator; using const_reverse_iterator = typename string_base::const_reverse_iterator; using allocator_type = Allocator; private: REXY_CPP20_CONSTEXPR void _copy_construct_string(const_pointer data, size_type len, size_type cap) noexcept(noexcept(this->allocate(0))); REXY_CPP20_CONSTEXPR basic_string& _copy_string(const_pointer s, size_type len) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); public: constexpr basic_string(void)noexcept; constexpr basic_string(rexy::steal data, size_type len)noexcept; constexpr basic_string(rexy::steal data, size_type len, size_type cap)noexcept; constexpr basic_string(rexy::steal data)noexcept; REXY_CPP20_CONSTEXPR basic_string(const_pointer data, size_type len)noexcept(noexcept(this->allocate(0))); REXY_CPP20_CONSTEXPR basic_string(const_pointer data, size_type len, size_type cap)noexcept(noexcept(this->allocate(0))); REXY_CPP20_CONSTEXPR basic_string(const_pointer data)noexcept(noexcept(this->allocate(0))); REXY_CPP20_CONSTEXPR explicit basic_string(size_type len)noexcept(noexcept(this->allocate(0))); REXY_CPP20_CONSTEXPR basic_string(size_type len, size_type cap)noexcept(noexcept(this->allocate(0))); template REXY_CPP20_CONSTEXPR basic_string(InputIt start, InputIt fin)noexcept(noexcept(this->allocate(0))); REXY_CPP20_CONSTEXPR basic_string(const basic_string_view& sv)noexcept(noexcept(this->allocate(0))); //normal copy and move ctors REXY_CPP20_CONSTEXPR basic_string(const basic_string& b)noexcept(noexcept(this->allocate(0))); constexpr basic_string(basic_string&& s)noexcept; REXY_CPP20_CONSTEXPR basic_string(const string_base&)noexcept(noexcept(this->allocate(0))); //dtor REXY_CPP20_CONSTEXPR ~basic_string(void)noexcept(noexcept(this->deallocate(nullptr, 0))); REXY_CPP20_CONSTEXPR basic_string& operator=(const basic_string& s) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); constexpr basic_string& operator=(basic_string&& s)noexcept; REXY_CPP20_CONSTEXPR basic_string& operator=(const string_base& s) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); REXY_CPP20_CONSTEXPR basic_string& operator=(const basic_string_view& sv) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); //Copy from c string REXY_CPP20_CONSTEXPR basic_string& operator=(const_pointer c) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); //Replace managed pointer. Frees existing value REXY_CPP20_CONSTEXPR void reset(pointer val = nullptr)noexcept(noexcept(this->deallocate(nullptr,0))); REXY_CPP20_CONSTEXPR void reset(pointer val, size_type len)noexcept(noexcept(this->deallocate(nullptr,0))); REXY_CPP20_CONSTEXPR bool resize(size_type newsize) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); REXY_CPP20_CONSTEXPR void append(const_pointer data, size_type len) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); REXY_CPP20_CONSTEXPR void append(const_pointer data) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); template REXY_CPP20_CONSTEXPR void append(InputIt start, InputIt fin) noexcept(noexcept(this->allocate(0)) && noexcept(this->deallocate(nullptr,0))); template REXY_CPP20_CONSTEXPR basic_string substring(size_type start, size_type end)const; REXY_CPP20_CONSTEXPR pointer release(void)noexcept(noexcept(this->allocate(0))); using detail::hasallocator::allocator; constexpr basic_string_view create_view(void)const noexcept; constexpr basic_string_view create_view(const_iterator start, const_iterator fin)const noexcept; }; //Like an expression template but not really template class string_cat_expr : public rexy::binary_expression { static_assert(std::is_same::value_type,typename std::decay_t::value_type>::value); private: using left_t = std::decay_t; using right_t = std::decay_t; public: using value_type = typename left_t::value_type; using size_type = decltype(typename left_t::size_type{0} + typename right_t::size_type{0}); using difference_type = decltype(typename left_t::difference_type{0} - typename right_t::difference_type{0}); using pointer = value_type*; using const_pointer = const value_type*; using reference = value_type&; using const_reference = const value_type&; using iterator = value_type*; using const_iterator = const value_type*; public: using binary_expression::binary_expression; constexpr string_cat_expr(const string_cat_expr&) = default; constexpr string_cat_expr(string_cat_expr&&) = default; constexpr size_type length(void)const noexcept; template REXY_CPP20_CONSTEXPR operator basic_string(void) noexcept(std::is_nothrow_constructible, typename basic_string::size_type>::value && std::is_nothrow_invocable>,decltype(*this)>::value); }; template string_cat_expr(Left&&,Right&&) -> string_cat_expr; namespace detail{ template constexpr int string_compare(Left&& left, Right&& right, size_t maxlen){ for(size_t i = 0;i < maxlen;++i){ const auto diff = left[i] - right[i]; if(diff != 0){ return diff; } if(left[i] == 0 || right[i] == 0){ return diff; } } return 0; } template constexpr size_t string_len(const Str* str){ if(!str){ return 0; } size_t i; for(i = 0;str[i] != 0;++i); return i; } } #if __cplusplus >= 202002L template concept BasicString = requires(const T& a){ {std::as_const(a).length()} -> std::convertible_to::size_type>; {std::as_const(a).c_str()} -> std::convertible_to::const_pointer>; {std::as_const(a)[0]} -> std::convertible_to::const_reference>; {std::as_const(a).begin()} -> std::convertible_to::const_iterator>; {std::as_const(a).end()} -> std::convertible_to::const_iterator>; }; template concept StringExpr = rexy::is_template_type::value; template concept String = BasicString || StringExpr; template struct is_string{ static constexpr bool value = BasicString; }; template static constexpr bool is_string_v = is_string::value; template struct are_strings{ static constexpr bool value = (BasicString && ...); }; template static constexpr bool are_strings_v = are_strings::value; template struct is_string_expr{ static constexpr bool value = StringExpr; }; template static constexpr bool is_string_expr_v = is_string_expr::value; template struct are_string_expr{ static constexpr bool value = (StringExpr && ...); }; template static constexpr bool are_string_expr_v = are_string_expr::value; //Compare template constexpr bool operator==(Str1&& left, Str2&& right){ if(left.length() != right.length()){ return false; } return !detail::string_compare(std::forward(left), std::forward(right), left.length()); } template constexpr bool operator==(Str1&& left, typename std::decay_t::const_pointer right){ if(right == nullptr){ return false; } const auto rlen = detail::string_len(right); if(rlen != left.length()){ return false; } const auto minlen = min(left.length(), rlen); return !detail::string_compare(left.c_str(), right, minlen+1); } template constexpr bool operator==(typename std::decay_t::const_pointer left, Str1&& right){ if(left == nullptr){ return false; } const auto llen = detail::string_len(left); if(llen != right.length()){ return false; } const auto minlen = min(right.length(), llen); return !detail::string_compare(right.c_str(), left, minlen+1); } template constexpr bool operator!=(Str1&& left, Str2&& right){ return !(std::forward(left) == std::forward(right)); } template constexpr bool operator!=(Str1&& left, typename std::decay_t::const_pointer right){ return !(std::forward(left) == right); } template constexpr bool operator!=(typename std::decay_t::const_pointer left, Str1&& right){ return !(left == std::forward(right)); } //String + string concat template constexpr auto operator+(Left&& l, Right&& r) //uses deduction guide whereas std::is_nothrow_constructible couldn't noexcept(noexcept(::new (nullptr) string_cat_expr(std::forward(l), std::forward(r)))) { return string_cat_expr(std::forward(l), std::forward(r)); } template constexpr auto operator+(typename std::decay_t::const_pointer left, Right&& right) noexcept(noexcept(::new (nullptr) string_cat_expr(rexy::basic_string_view(left), std::forward(right)))) { return string_cat_expr(rexy::basic_string_view(left), std::forward(right)); } template constexpr auto operator+(Left&& left, typename std::decay_t::const_pointer right) noexcept(noexcept(::new (nullptr) string_cat_expr(std::forward(left), rexy::basic_string_view(right)))) { return rexy::string_cat_expr(std::forward(left), rexy::basic_string_view(right)); } //String concat assign template REXY_CPP20_CONSTEXPR decltype(auto) operator+=(Left& l, Right&& r) noexcept(noexcept(l + std::forward(r)) && std::is_nothrow_assignable(r))>::value) { return l = (l + std::forward(r)); } template REXY_CPP20_CONSTEXPR decltype(auto) operator+=(Left& l, typename std::decay_t::const_pointer r) noexcept(noexcept(l + r) && std::is_nothrow_assignable::value) { return l = (l + r); } #else //__cplusplus == 202002L #define HAS_MEMFUN_WITH_RET(type, ret, fun, ...) \ template \ struct has_##fun##_f{ \ static std::false_type check(...); \ template \ static auto check(type* u) -> std::enable_if_t().fun(__VA_ARGS__)),ret>,std::true_type>; \ \ static constexpr bool value = decltype(check(std::declval*>()))::value; \ }; \ template \ static constexpr bool has_##fun##_f_v = has_##fun##_f::value #define HAS_MEMOP_WITH_RET(type, ret, opname, op, ...) \ template \ struct has_##opname##_f{ \ static std::false_type check(...); \ template \ static auto check(type* u) -> std::enable_if_t().operator op(__VA_ARGS__)),ret>,std::true_type>; \ \ static constexpr bool value = decltype(check(std::declval*>()))::value; \ }; \ template \ static constexpr bool has_##opname##_f_v = has_##opname##_f::value HAS_MEMFUN_WITH_RET(U, typename U::size_type, length); HAS_MEMFUN_WITH_RET(U, typename U::const_pointer, c_str); HAS_MEMOP_WITH_RET(U, typename U::const_reference, indexop, [], 0); HAS_MEMFUN_WITH_RET(U, typename U::const_iterator, begin); HAS_MEMFUN_WITH_RET(U, typename U::const_iterator, end); #undef HAS_MEMFUN_WITH_RET #undef HAS_MEMOP_WITH_RET template struct is_string{ static constexpr bool value = has_length_f_v && has_c_str_f_v && has_indexop_f_v && has_begin_f_v && has_end_f_v; }; template static constexpr bool is_string_v = is_string::value; template struct are_strings{ static constexpr bool value = (is_string::value && ...); }; template static constexpr bool are_strings_v = are_strings::value; template struct is_string_expr{ static constexpr bool value = rexy::is_template_type::value; }; template static constexpr bool is_string_expr_v = is_string_expr::value; template struct are_string_expr{ static constexpr bool value = (is_string_expr::value && ...); }; template static constexpr bool are_string_expr_v = are_string_expr::value; //Compare template::value,int> = 0> constexpr bool operator==(Str1&& left, Str2&& right){ if(left.length() != right.length()){ return false; } return !detail::string_compare(std::forward(left), std::forward(right), left.length()); } template::value,int> = 0> constexpr bool operator==(Str1&& left, typename std::decay_t::const_pointer right){ if(right == nullptr){ return false; } const auto rlen = detail::string_len(right); if(rlen != left.length()){ return false; } const auto minlen = min(left.length(), rlen); return !detail::string_compare(left.c_str(), right, minlen+1); } template::value,int> = 0> constexpr bool operator==(typename std::decay_t::const_pointer left, Str1&& right){ if(left == nullptr){ return false; } const auto llen = detail::string_len(left); if(llen != right.length()){ return false; } const auto minlen = min(right.length(), llen); return !detail::string_compare(right.c_str(), left, minlen+1); } template::value,int> = 0> constexpr bool operator!=(Str1&& left, Str2&& right)noexcept{ return !(std::forward(left) == std::forward(right)); } template::value,int> = 0> constexpr bool operator!=(Str1&& left, typename std::decay_t::const_pointer right)noexcept{ return !(std::forward(left) == right); } template::value,int> = 0> constexpr bool operator!=(typename std::decay_t::const_pointer left, Str1&& right)noexcept{ return !(left == std::forward(right)); } //String + string concat template::value,int> = 0> constexpr auto operator+(Left&& l, Right&& r) //uses deduction guide whereas std::is_nothrow_constructible couldn't noexcept(noexcept(::new (nullptr) string_cat_expr(std::forward(l), std::forward(r)))) { return string_cat_expr(std::forward(l), std::forward(r)); } //String + char pointer template::value,int> = 0> constexpr auto operator+(typename std::decay_t::const_pointer left, Right&& right) noexcept(noexcept(::new (nullptr) string_cat_expr(rexy::basic_string_view(left), std::forward(right)))) { return string_cat_expr(rexy::basic_string_view(left), std::forward(right)); } //Char pointer + string template::value,int> = 0> constexpr auto operator+(Left&& left, typename std::decay_t::const_pointer right) noexcept(noexcept(::new (nullptr) string_cat_expr(std::forward(left), rexy::basic_string_view(right)))) { return rexy::string_cat_expr(std::forward(left), rexy::basic_string_view(right)); } //String + expr concat template::value && is_string_expr::value,int> = 0> constexpr auto operator+(Left&& left, Right&& right){ return string_cat_expr(std::forward(left), std::forward(right)); } //Expr + string template::value && is_string::value,int> = 0> constexpr auto operator+(Left&& left, Right&& right){ return string_cat_expr(std::forward(left), std::forward(right)); } //Expr + expr template::value,int> = 0> constexpr auto operator+(Left&& left, Right&& right){ return string_cat_expr(std::forward(left), std::forward(right)); } //Expr + char pointer template::value,int> = 0> constexpr auto operator+(Left&& left, typename std::decay_t::const_pointer right){ return string_cat_expr(std::forward(left), rexy::basic_string_view(right)); } //char pointer + Expr template::value,int> = 0> constexpr auto operator+(typename std::decay_t::const_pointer left, Right&& right){ return string_cat_expr(rexy::basic_string_view(left), std::forward(right)); } //String concat assignment template::value,int> = 0> REXY_CPP20_CONSTEXPR decltype(auto) operator+=(Left& l, Right&& r) noexcept(noexcept(l + std::forward(r)) && std::is_nothrow_assignable(r))>::value) { return l = (l + std::forward(r)); } template::value && is_string_expr::value,int> = 0> REXY_CPP20_CONSTEXPR decltype(auto) operator+=(Left& l, Right&& r) noexcept(noexcept(l + std::forward(r)) && std::is_nothrow_assignable(r))>::value) { return l = (l + std::forward(r)); } template::value, int> = 0> REXY_CPP20_CONSTEXPR decltype(auto) operator+=(Left& l, typename std::decay_t::const_pointer r) noexcept(noexcept(l + r) && std::is_nothrow_assignable::value) { return l = (l + r); } #endif //__cplusplus == 202002L } #include "string_base.tpp" namespace{ template std::ostream& operator<<(std::ostream& os, const rexy::basic_string& str){ return os << str.c_str(); } } #endif