diff --git a/include/ak_toolkit/static_string.hpp b/include/ak_toolkit/static_string.hpp index b6de63a..ed0d8d6 100644 --- a/include/ak_toolkit/static_string.hpp +++ b/include/ak_toolkit/static_string.hpp @@ -19,16 +19,16 @@ # define AK_TOOLKIT_STRING_VIEW_OPERATIONS() # elif AK_TOOLKIT_CONFIG_USING_STRING_VIEW == 1 # include -# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr operator ::std::string_view () const { return ::std::string_view(c_str(), N); } +# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr operator ::std::string_view () const { return ::std::string_view(c_str(), len()); } # elif AK_TOOLKIT_CONFIG_USING_STRING_VIEW == 2 # include -# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr operator ::std::experimental::string_view () const { return ::std::experimental::string_view(c_str(), N); } +# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr operator ::std::experimental::string_view () const { return ::std::experimental::string_view(c_str(), len()); } # elif AK_TOOLKIT_CONFIG_USING_STRING_VIEW == 3 # include -# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr operator ::boost::string_ref () const { return ::boost::string_ref(c_str(), N); } +# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr operator ::boost::string_ref () const { return ::boost::string_ref(c_str(), len()); } # elif AK_TOOLKIT_CONFIG_USING_STRING_VIEW == 4 # include -# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() operator ::std::string () const { return ::std::string(c_str(), N); } +# define AK_TOOLKIT_STRING_VIEW_OPERATIONS() operator ::std::string () const { return ::std::string(c_str(), len()); } # endif # else # define AK_TOOLKIT_STRING_VIEW_OPERATIONS() @@ -82,6 +82,10 @@ namespace detail # define AK_TOOLKIT_ASSERT(CHECK) ((CHECK) ? void(0) : []{assert(!#CHECK);}()) #endif +/** Return the length of the given string */ +constexpr size_t static_strlen(const char *str) { + return *str == '\0' ? 0 : static_strlen(str + 1) + 1; +} struct literal_ref {}; struct char_array {}; @@ -97,14 +101,31 @@ class string template class string { +public: const char (&_lit)[N + 1]; + const size_t _len; + const size_t _offset; + explicit constexpr string(const char (&lit)[N + 1], size_t offset, size_t len) + : _lit((AK_TOOLKIT_ASSERT(lit[N] == 0), lit)), + _offset((AK_TOOLKIT_ASSERT(offset <= N), offset)), + _len((AK_TOOLKIT_ASSERT(offset + len <= N), len)) + { } public: - constexpr string(const char (&lit)[N + 1]) : _lit((AK_TOOLKIT_ASSERT(lit[N] == 0), lit)) {} - constexpr char operator[](int i) const { return AK_TOOLKIT_ASSERT(i >= 0 && i < N), _lit[i]; } + constexpr string(const char (&lit)[N + 1]) : _lit((AK_TOOLKIT_ASSERT(lit[N] == 0), lit)), _offset(0), _len(N) {} + constexpr char operator[](int i) const { return AK_TOOLKIT_ASSERT(i < _len), _lit[i+_offset]; } AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr ::std::size_t size() const { return N; }; - constexpr const char* c_str() const { return _lit; } + constexpr ::std::size_t len() const { return _len; }; + constexpr const char* c_str() const { return _lit + _offset; } constexpr operator const char * () const { return c_str(); } + + constexpr string substr(size_t start, size_t len) const { + return string(_lit, start + _offset, len); + } + + constexpr string substr(size_t start) const { + return string(_lit, start + _offset, len() - start); + } }; template @@ -124,40 +145,65 @@ constexpr string_literal literal(const char (&lit)[N_PLUS_1]) template class string + { +public: char _array[N + 1]; struct private_ctor {}; - template - constexpr explicit string(private_ctor, string const& l, string const& r, detail::int_sequence, detail::int_sequence) - : _array{l[Il]..., r[Ir]..., 0} + template + constexpr explicit string(private_ctor, string const& l, string const& r, detail::int_sequence) + : _array{concat_elem(l, r, Il)..., 0} { } - - template - constexpr explicit string(private_ctor, string const& l, detail::int_sequence) - : _array{l[Il]..., 0} - { + + template + constexpr char concat_elem(string const& l, string const& r, size_t i) { + return i < l.len() + ? l[i] + : i - l.len() < r.len() + ? r[i - l.len()] + : 0; } public: template ::type = true> constexpr explicit string(string l, string r) - : string(private_ctor{}, l, r, detail::make_int_sequence{}, detail::make_int_sequence{}) + : string(private_ctor{}, l, r, detail::make_int_sequence{}) { } constexpr string(string_literal l) // converting - : string(private_ctor{}, l, detail::make_int_sequence{}) + : string(private_ctor{}, l, literal(""), detail::make_int_sequence{}) { } - + + // Expanding is always ok, shrinking only down to l.len() + template + constexpr string(string l) + : string((AK_TOOLKIT_ASSERT(N >= OldN || N >= l.len()), private_ctor{}), l, literal(""), detail::make_int_sequence{}) + { + } + constexpr ::std::size_t size() const { return N; } + constexpr ::std::size_t len() const { return static_strlen(_array); } constexpr const char* c_str() const { return _array; } constexpr operator const char * () const { return c_str(); } AK_TOOLKIT_STRING_VIEW_OPERATIONS() constexpr char operator[] (int i) const { return AK_TOOLKIT_ASSERT(i >= 0 && i < N), _array[i]; } + + constexpr string substr(size_t start, size_t len) const { + // Return implicitly converts back to an array_string, which is + // needed because the _array referenced might be a temporary, so + // we shouldn't return a pointer to it + return literal(_array).substr(start, len); + } + + constexpr string substr(size_t start) const { + return literal(_array).substr(start); + } + }; template diff --git a/test/test_static_string.cpp b/test/test_static_string.cpp index 5147b48..a78f5ca 100644 --- a/test/test_static_string.cpp +++ b/test/test_static_string.cpp @@ -1,5 +1,5 @@ #include - +#include namespace sstr = ak_toolkit::static_str; #if __cplusplus >= 201703L @@ -16,6 +16,65 @@ static_assert(NAME[0] == 'A', "***"); constexpr auto CD_ = CD + ""; static_assert(CD_.size() == CD.size(), "***"); +constexpr auto CDE = NAME.substr(2, 2) + "E"; + +/** + * Helper to figure out the strrchr result. A helper is needed so we can + * put the result of the recursive call in a variable, which is not + * otherwise allowed in C++11. The alternative, to do the recursive call + * twice, makes the algorithm exponential, which breaks for a string + * length of more than a few characters. */ +template +constexpr auto static_strrchrnul_helper(T1 const& s, T2 const& recursive, char c) +-> decltype(s.substr(0)) { + return (recursive.len() == 0 && s[0] == c) ? s : recursive; +} + +/** + * Return the last occurence of c in the given string, or an empty + * string if the character does not occur. This should behave just like + * the regular strrchrnul function. + */ +template +constexpr auto static_strrchrnul(sstr::string const& s, char c) +-> decltype(s.substr(0)) +{ + /* C++14 version + // If we reach the end of the string, return an empty string + if (s.len() == 0) return s; + // Otherwise there is a remainder string, check that + auto recursive = static_strrchrnul(s.substr(1), c); + // If c was found in the remainder, return that + if (recursive.len() != 0) + return recursive; + // If c was not found in the remainder, but we find it here, return + // the current string. + if (s[0] == c) + return s; + // Otherwise, return an empty string (which is conveniently what + // recursive contains). + return recursive; + */ + return s.len() == 0 ? s : static_strrchrnul_helper(s, static_strrchrnul(s.substr(1), c), c); +} + +/** + * Return one past the last separator in the given path, or the start of + * the path if it contains no separator. + * Unlike the regular basename, this does not handle trailing separators + * specially (so it returns an empty string if the path ends in a + * separator). + */ +template +constexpr auto static_basename(sstr::string const& path) +-> decltype(static_strrchrnul(path, '/')) +{ + return (static_strrchrnul(path, '/').len() > 0 + ? static_strrchrnul(path, '/').substr(1) + : path.substr(0) + ); +} + #include #include @@ -23,5 +82,19 @@ int main () { std::ostringstream os; os << NAME; + std::cout << os.str() << std::endl; assert(os.str() == "ABCDEFGH"); -} \ No newline at end of file + + os.str(""); + os << CDE; + std::cout << os.str() << std::endl; + assert(os.str() == "CDE"); + + std::cout << "original: " << __FILE__ << std::endl; + constexpr auto file = static_basename(sstr::literal(__FILE__) + "") + ""; + std::cout << "file: " << file << std::endl; + std::cout << "sizeof(file): " << sizeof(file) << std::endl; + constexpr auto small_file = sstr::array_string(file); + std::cout << "small_file: " << small_file << std::endl; + std::cout << "sizeof(small_file): " << sizeof(small_file) << std::endl; +}