Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 64 additions & 18 deletions include/ak_toolkit/static_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@
# define AK_TOOLKIT_STRING_VIEW_OPERATIONS()
# elif AK_TOOLKIT_CONFIG_USING_STRING_VIEW == 1
# include <string_view>
# 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 <experimental/string_view>
# 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 <boost/utility/string_ref.hpp>
# 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 <string>
# 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()
Expand Down Expand Up @@ -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 {};
Expand All @@ -97,14 +101,31 @@ class string
template <int N>
class string<N, literal_ref>
{
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 <int N>
Expand All @@ -124,40 +145,65 @@ constexpr string_literal<N_PLUS_1 - 1> literal(const char (&lit)[N_PLUS_1])

template <int N>
class string<N, char_array>

{
public:
char _array[N + 1];
struct private_ctor {};

template <int M, int... Il, int... Ir, typename TL, typename TR>
constexpr explicit string(private_ctor, string<M, TL> const& l, string<N - M, TR> const& r, detail::int_sequence<Il...>, detail::int_sequence<Ir...>)
: _array{l[Il]..., r[Ir]..., 0}
template <int NL, int NR, int... Il, int... Ir, typename TL, typename TR>
constexpr explicit string(private_ctor, string<NL, TL> const& l, string<NR, TR> const& r, detail::int_sequence<Il...>)
: _array{concat_elem(l, r, Il)..., 0}
{
}

template <int... Il, typename T>
constexpr explicit string(private_ctor, string<N, T> const& l, detail::int_sequence<Il...>)
: _array{l[Il]..., 0}
{

template <int NL, int NR, typename TL, typename TR>
constexpr char concat_elem(string<NL, TL> const& l, string<NR, TR> const& r, size_t i) {
return i < l.len()
? l[i]
: i - l.len() < r.len()
? r[i - l.len()]
: 0;
}

public:
template <int M, typename TL, typename TR, typename std::enable_if<(M <= N), bool>::type = true>
constexpr explicit string(string<M, TL> l, string<N - M, TR> r)
: string(private_ctor{}, l, r, detail::make_int_sequence<M>{}, detail::make_int_sequence<N - M>{})
: string(private_ctor{}, l, r, detail::make_int_sequence<N>{})
{
}

constexpr string(string_literal<N> l) // converting
: string(private_ctor{}, l, detail::make_int_sequence<N>{})
: string(private_ctor{}, l, literal(""), detail::make_int_sequence<N>{})
{
}


// Expanding is always ok, shrinking only down to l.len()
template <int OldN, typename T>
constexpr string(string<OldN, T> l)
: string((AK_TOOLKIT_ASSERT(N >= OldN || N >= l.len()), private_ctor{}), l, literal(""), detail::make_int_sequence<N>{})
{
}

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 <int N>
Expand Down
77 changes: 75 additions & 2 deletions test/test_static_string.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#include <ak_toolkit/static_string.hpp>

#include <iostream>
namespace sstr = ak_toolkit::static_str;

#if __cplusplus >= 201703L
Expand All @@ -16,12 +16,85 @@ 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 <typename T1, typename T2>
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 <int N, typename T>
constexpr auto static_strrchrnul(sstr::string<N, T> 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 <int N, typename T>
constexpr auto static_basename(sstr::string<N, T> const& path)
-> decltype(static_strrchrnul(path, '/'))
{
return (static_strrchrnul(path, '/').len() > 0
? static_strrchrnul(path, '/').substr(1)
: path.substr(0)
);
}

#include <sstream>
#include <cassert>

int main ()
{
std::ostringstream os;
os << NAME;
std::cout << os.str() << std::endl;
assert(os.str() == "ABCDEFGH");
}

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.len()>(file);
std::cout << "small_file: " << small_file << std::endl;
std::cout << "sizeof(small_file): " << sizeof(small_file) << std::endl;
}