Skip to content

Instantly share code, notes, and snippets.

@goto-bus-stop
Created June 19, 2020 13:48
Show Gist options
  • Save goto-bus-stop/9a1db4cf2aa07ce6cc1a539338b0a23e to your computer and use it in GitHub Desktop.
Save goto-bus-stop/9a1db4cf2aa07ce6cc1a539338b0a23e to your computer and use it in GitHub Desktop.
A C++17 class that quacks like a `std::string` but is actually a null-terminated `char[N]`.
#pragma once
#include <array>
#include <cstddef>
#include <string_view>
/// A string type that takes a fixed amount of stack space.
template <typename Type, size_t Capacity> class basic_fixed_zstring {
static_assert(!std::is_array_v<Type>);
static_assert(std::is_trivial_v<Type> && std::is_standard_layout_v<Type>);
using traits_type = std::char_traits<Type>;
using size_type = size_t;
using reference = Type&;
using const_reference = const Type&;
using pointer = Type*;
using const_pointer = const Type*;
using view_type = std::basic_string_view<Type>;
using string_type = std::basic_string<Type>;
using storage_type = std::array<Type, Capacity>;
using iterator = typename storage_type::iterator;
using const_iterator = typename storage_type::const_iterator;
static constexpr size_type npos = (size_type)-1;
private:
storage_type data_;
constexpr view_type as_view() const noexcept { return operator view_type(); }
constexpr void assert_capacity(size_t len) {
#ifdef NDEBUG
#elif __cpp_exceptions
if (len >= Capacity - 1) {
throw std::length_error("zstring: exceeded capacity");
}
#endif
}
constexpr void assert_not_empty() {
#ifdef NDEBUG
#elif __cpp_exceptions
if (empty()) {
throw std::logic_error(
"zstring: attempted front() or back() access on empty zstring");
}
#endif
}
constexpr void assert_in_range(size_t offset) {
#ifdef NDEBUG
#elif __cpp_exceptions
if (offset < 0 || offset >= length()) {
throw std::out_of_range("zstring: attempted to access out of bounds");
}
#endif
}
public:
/// Initialize an empty string.
constexpr basic_fixed_zstring() { clear(); }
/// Copy a null-terminated character array `input` into this string.
///
/// If the character array does not fit inside this zstring's capacity, the
/// behaviour is undefined.
constexpr basic_fixed_zstring(const_pointer input) {
auto len = traits_type::length(input);
assert_capacity(len);
traits_type::copy(data_.data(), input, len + 1); // include NUL
}
/// Copy a string_view's contents into this string.
///
/// If the string_view's contents do not fit inside this zstring's capacity,
/// the behaviour is undefined.
constexpr basic_fixed_zstring(view_type view) {
assert_capacity(view.length());
view.copy(data_.data(), Capacity);
data_[view.size()] = 0; // add NUL
}
/// Copy another fixed zstring's contents into this string.
constexpr basic_fixed_zstring(const basic_fixed_zstring&) = default;
/// Copy another zstring's contents into this string, including zstrings of
/// different capacities.
///
/// If the other zstring has more characters than fit in this zstring's
/// capacity, the behaviour is undefined.
template <size_t OtherCapacity>
constexpr basic_fixed_zstring(
const basic_fixed_zstring<Type, OtherCapacity>& other) {
auto len = other.length();
assert_capacity(len);
traits_type::copy(data_.data(), other.c_str(), len + 1); // include NUL
}
// introspection
/// Get the maximum possible size for this string.
[[nodiscard]] constexpr size_type max_size() const noexcept {
return Capacity - 1;
}
/// Get the number of characters in this string.
[[nodiscard]] constexpr size_type size() const { return length(); }
/// Get the number of characters in this string.
[[nodiscard]] constexpr size_type length() const {
return traits_type::length(data_.data());
}
/// Get one character. Accessing characters beyond the end of the string is
/// undefined behaviour.
[[nodiscard]] constexpr reference operator[](size_type pos) {
assert_in_range(pos);
return data_[pos];
}
/// Get one character. Accessing characters beyond the end of the string is
/// undefined behaviour.
[[nodiscard]] constexpr const_reference operator[](size_type pos) const {
assert_in_range(pos);
return data_[pos];
}
/// Get the first character. If this zstring is empty, the behaviour is
/// undefined.
[[nodiscard]] constexpr reference front() {
assert_not_empty();
return data_[0];
}
[[nodiscard]] constexpr const_reference front() const {
assert_not_empty();
return data_[0];
}
/// Get the last character. If this zstring is empty, the behaviour is
/// undefined.
[[nodiscard]] constexpr reference back() {
assert_not_empty();
return data_[length() - 1];
}
[[nodiscard]] constexpr const_reference back() const {
assert_not_empty();
return data_[length() - 1];
}
[[nodiscard]] constexpr bool empty() const { return data_[0] == 0; }
/// Access the underlying character array.
[[nodiscard]] constexpr pointer data() noexcept { return data_.data(); }
[[nodiscard]] constexpr const_pointer data() const noexcept { return data_.data(); }
/// Get a null-terminated C-style string.
[[nodiscard]] constexpr const_pointer c_str() const noexcept { return data_.data(); }
/// Delete all the characters.
constexpr void clear() { data_[0] = 0; }
/// Add a character at the end. If the length of the zstring exceeds the capacity, the behaviour is undefined.
constexpr void push_back(Type ch) {
auto len = length();
assert_capacity(len + 1);
data_[len] = ch;
data_[len + 1] = 0;
}
/// Delete the last character. If the zstring is empty, the behaviour is undefined.
constexpr void pop_back() {
auto len = length();
data_[len - 1] = 0;
}
template <size_t OutCapacity = Capacity>
[[nodiscard]] constexpr basic_fixed_zstring<Type, OutCapacity> substr(size_t pos = 0, size_t count = npos) {
basic_fixed_zstring<Type, OutCapacity> sub;
sub += as_view().substr(pos, count);
return sub;
}
template <size_t Pos, size_t Count>
[[nodiscard]] constexpr basic_fixed_zstring<Type, Count + 1> substr() {
basic_fixed_zstring<Type, Count + 1> sub;
sub += as_view().substr(Pos, Count);
return sub;
}
[[nodiscard]] constexpr bool operator==(std::string_view text) { return strncmp(data(), text.data(), text.size()) == 0 && data_[text.size()] == 0; }
[[nodiscard]] constexpr bool operator!=(std::string_view text) { return strncmp(data(), text.data(), text.size()) != 0 || data_[text.size()] != 0; }
/// Delete the character at position `position`.
constexpr iterator erase(iterator position) {
assert_in_range(position - begin());
return erase(position, position);
}
constexpr const_iterator erase(const_iterator first, const_iterator last) {
auto start_index = first - begin();
auto end_index = last + 1 - begin();
assert_in_range(start_index);
assert_in_range(end_index - 1);
auto remaining_chars = end() - last + 1;
traits_type::move(data() + start_index, data() + end_index,
remaining_chars);
return begin() + start_index;
}
constexpr iterator erase(iterator first, iterator last) {
// convert to const iters
const auto start_offset = first - begin();
const auto end_offset = last - begin();
auto result = erase(cbegin() + start_offset, cbegin() + end_offset);
return begin() + (result - cbegin()); // convert to non const iter
}
constexpr auto& erase(size_type index = 0,
size_type count = view_type::npos) {
if (count == view_type::npos)
count = length() - index;
erase(begin() + index, begin() + index + count);
return *this;
}
constexpr auto& operator+=(Type ch) {
this->push_back(ch);
return *this;
}
constexpr auto& operator+=(const_pointer chars) {
auto len = length();
auto add_len = traits_type::length(chars);
assert_capacity(add_len + len);
traits_type::copy(&data_[len], chars, add_len + 1);
return *this;
}
constexpr auto& operator+=(view_type view) {
auto len = length();
auto add_len = view.length();
assert_capacity(add_len + len);
traits_type::copy(&data_[len], view.data(), add_len);
data_[len + add_len] = 0; // add NUL
return *this;
}
[[nodiscard]] constexpr iterator begin() noexcept { return data_.begin(); }
[[nodiscard]] constexpr const_iterator begin() const noexcept { return data_.begin(); }
[[nodiscard]] constexpr const_iterator cbegin() const noexcept { return data_.cbegin(); }
[[nodiscard]] constexpr iterator end() noexcept { return data_.end(); }
[[nodiscard]] constexpr const_iterator end() const noexcept { return data_.end(); }
[[nodiscard]] constexpr const_iterator cend() const noexcept { return data_.cend(); }
/// Convert to a `std::string_view`.
constexpr operator view_type() const noexcept {
return {data_.data(), length()};
}
/// Convert to a `std::string`.
constexpr string_type string() const noexcept { return {data_.data()}; }
/// Convert to a raw `const T*` pointer.
constexpr operator const_pointer() const noexcept { return c_str(); }
constexpr size_type find(view_type view, size_t pos = 0) const noexcept {
return as_view().find(view, pos);
}
constexpr size_type find(Type ch, size_t pos = 0) const noexcept {
return as_view().find(ch, pos);
}
constexpr size_type find(const_pointer str, size_t pos, size_t count) const {
return as_view().find(str, pos, count);
}
constexpr size_type find(const_pointer str, size_t pos = 0) const {
return as_view().find(str, pos);
}
constexpr size_type rfind(view_type view, size_t pos = view_type::npos) const
noexcept {
return as_view().rfind(view, pos);
}
constexpr size_type rfind(Type ch, size_t pos = view_type::npos) const
noexcept {
return as_view().rfind(ch, pos);
}
constexpr size_type rfind(const_pointer str, size_t pos, size_t count) const {
return as_view().rfind(str, pos, count);
}
constexpr size_type rfind(const_pointer str,
size_t pos = view_type::npos) const {
return as_view().rfind(str, pos);
}
constexpr size_type find_first_of(view_type view, size_t pos = 0) const
noexcept {
return as_view().find_first_of(view, pos);
}
constexpr size_type find_first_of(Type ch, size_t pos = 0) const noexcept {
return as_view().find_first_of(ch, pos);
}
constexpr size_type find_first_of(const_pointer str, size_t pos,
size_t count) const {
return as_view().find_first_of(str, pos, count);
}
constexpr size_type find_first_of(const_pointer str, size_t pos = 0) const {
return as_view().find_first_of(str, pos);
}
constexpr size_type find_last_of(view_type view,
size_t pos = view_type::npos) const
noexcept {
return as_view().find_last_of(view, pos);
}
constexpr size_type find_last_of(Type ch, size_t pos = view_type::npos) const
noexcept {
return as_view().find_last_of(ch, pos);
}
constexpr size_type find_last_of(const_pointer str, size_t pos,
size_t count) const {
return as_view().find_last_of(str, pos, count);
}
constexpr size_type find_last_of(const_pointer str,
size_t pos = view_type::npos) const {
return as_view().find_last_of(str, pos);
}
constexpr size_type find_first_not_of(view_type view, size_t pos = 0) const
noexcept {
return as_view().find_first_not_of(view, pos);
}
constexpr size_type find_first_not_of(Type ch, size_t pos = 0) const
noexcept {
return as_view().find_first_not_of(ch, pos);
}
constexpr size_type find_first_not_of(const_pointer str, size_t pos,
size_t count) const {
return as_view().find_first_not_of(str, pos, count);
}
constexpr size_type find_first_not_of(const_pointer str,
size_t pos = 0) const {
return as_view().find_first_not_of(str, pos);
}
constexpr size_type find_last_not_of(view_type view,
size_t pos = view_type::npos) const
noexcept {
return as_view().find_last_not_of(view, pos);
}
constexpr size_type find_last_not_of(Type ch,
size_t pos = view_type::npos) const
noexcept {
return as_view().find_last_not_of(ch, pos);
}
constexpr size_type find_last_not_of(const_pointer str, size_t pos,
size_t count) const {
return as_view().find_last_not_of(str, pos, count);
}
constexpr size_type find_last_not_of(const_pointer str,
size_t pos = view_type::npos) const {
return as_view().find_last_not_of(str, pos);
}
};
template <size_t Capacity>
using fixed_zstring = basic_fixed_zstring<char, Capacity>;
template <size_t Capacity>
using fixed_zwstring = basic_fixed_zstring<wchar_t, Capacity>;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment