Created
June 19, 2020 13:48
-
-
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]`.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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