blob: d36b4dc9920d5a1dc407c5e303d1d34a5e97774c [file] [log] [blame]
#pragma once
#include <fmt/core.h>
#include <cstddef>
#include <limits>
#include <string>
#include <type_traits>
#ifndef NDEBUG
#include <stdexcept>
#endif
namespace stdplus
{
namespace detail
{
template <typename CharT>
constexpr std::ptrdiff_t zstring_find_term(const CharT* str, std::size_t min,
std::size_t max) noexcept
{
for (size_t i = 0; i < min; ++i)
{
if (str[i] == '\0')
{
return -1;
}
}
for (size_t i = min; i < max; ++i)
{
if (str[i] == '\0')
{
return i;
}
}
return -1;
}
#ifndef NDEBUG
template <typename CharT>
constexpr std::size_t zstring_validate(const CharT* str, std::size_t min,
std::size_t max)
{
auto ret = zstring_find_term(str, min, max);
if (ret < 0)
{
throw std::invalid_argument("stdplus::zstring");
}
return ret;
}
#endif
template <typename T, typename CharT, typename Traits>
struct same_string : std::false_type
{};
template <typename CharT, typename Traits, typename Allocator>
struct same_string<std::basic_string<CharT, Traits, Allocator>, CharT, Traits> :
std::true_type
{};
} // namespace detail
/**
* @brief Provides a class for expressing an unbounded length string that has
* a nul terminating character. This is different from zstring_view in that it
* never computes the length, which is slightly cheaper if the string will just
* be downcast to a `const char *`.
*/
template <typename CharT,
typename Traits = std::char_traits<std::remove_const_t<CharT>>>
class basic_zstring
{
private:
using decay_t = std::remove_const_t<CharT>;
public:
using traits_type = Traits;
using value_type = CharT;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using size_type = std::size_t;
template <typename T, size_type N>
constexpr basic_zstring(T (&str)[N])
#ifdef NDEBUG
noexcept
#endif
:
data_(str)
{
#ifndef NDEBUG
detail::zstring_validate(str, 0, N);
#endif
}
template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
constexpr basic_zstring(T str) noexcept : data_(str)
{}
template <typename T,
std::enable_if_t<detail::same_string<std::remove_cvref_t<T>,
decay_t, Traits>::value,
bool> = true>
constexpr basic_zstring(T&& str)
#ifdef NDEBUG
noexcept
#endif
:
data_(str.data())
{
#ifndef NDEBUG
detail::zstring_validate(str.data(), str.size(), str.size() + 1);
#endif
}
constexpr operator basic_zstring<const CharT, Traits>() const noexcept
{
return basic_zstring<const CharT, Traits>(data_);
}
constexpr reference operator[](size_type pos) const noexcept
{
return data_[pos];
}
constexpr reference front() const noexcept
{
return data_[0];
}
constexpr pointer data() const noexcept
{
return data_;
}
constexpr const_pointer c_str() const noexcept
{
return data_;
}
[[nodiscard]] constexpr bool empty() const noexcept
{
return data_ == nullptr || data_[0] == '\0';
}
constexpr basic_zstring suffix(size_type size) const noexcept
{
return data_ + size;
}
constexpr basic_zstring<const CharT, Traits>
csuffix(size_type size) const noexcept
{
return data_ + size;
}
constexpr int compare(const_pointer other,
size_type other_size) const noexcept
{
for (size_t i = 0; i < other_size; ++i)
{
if (data_[i] == '\0')
{
return -1;
}
if (!Traits::eq(other[i], data_[i]))
{
return Traits::lt(other[i], data_[i]) ? 1 : -1;
}
}
if (data_[other_size] == '\0')
{
return 0;
}
return 1;
}
constexpr int compare(const_pointer other) const noexcept
{
auto data = data_;
while (true)
{
if (data[0] == '\0')
{
if (other[0] == '\0')
{
return 0;
}
return -1;
}
if (other[0] == '\0')
{
return 1;
}
if (!Traits::eq(other[0], data[0]))
{
return Traits::lt(other[0], data[0]) ? 1 : -1;
}
data++;
other++;
}
}
template <typename CharT1>
constexpr Traits::comparison_category
operator<=>(basic_zstring<CharT1, Traits> rhs) const noexcept
{
return compare(rhs.data()) <=> 0;
}
template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
constexpr Traits::comparison_category operator<=>(T rhs) const noexcept
{
return compare(rhs) <=> 0;
}
template <typename Allocator>
constexpr Traits::comparison_category operator<=>(
const std::basic_string<decay_t, Traits, Allocator>& rhs) const noexcept
{
return compare(rhs.data(), rhs.size()) <=> 0;
}
constexpr Traits::comparison_category
operator<=>(std::basic_string_view<decay_t, Traits> rhs) const noexcept
{
return compare(rhs.data(), rhs.size()) <=> 0;
}
constexpr bool operator==(const auto& rhs) const noexcept
{
return (*this <=> rhs) == 0;
}
private:
pointer data_;
};
template <typename CharT, typename Traits>
std::basic_ostream<std::remove_const_t<CharT>, Traits>&
operator<<(std::basic_ostream<std::remove_const_t<CharT>, Traits>& os,
basic_zstring<CharT, Traits> v)
{
return os << v.c_str();
}
#define zstring_all(char_t, pfx) \
using pfx##zstring = basic_zstring<char_t>; \
using const_##pfx##zstring = basic_zstring<const char_t>;
zstring_all(char, );
zstring_all(char8_t, u8);
zstring_all(char16_t, u16);
zstring_all(char32_t, u32);
zstring_all(wchar_t, w);
#undef zstring_all
} // namespace stdplus
namespace fmt
{
template <typename CharT, typename Traits>
struct formatter<stdplus::basic_zstring<CharT, Traits>,
std::remove_const_t<CharT>> :
formatter<const CharT*, std::remove_const_t<CharT>>
{
template <typename FormatContext>
inline auto format(stdplus::basic_zstring<CharT, Traits> str,
FormatContext& ctx) const
{
return formatter<const CharT*, std::remove_const_t<CharT>>::format(
str.c_str(), ctx);
}
};
} // namespace fmt
namespace std
{
template <typename T, typename CharT>
struct formatter;
template <typename CharT, typename Traits>
struct formatter<stdplus::basic_zstring<CharT, Traits>,
std::remove_const_t<CharT>> :
formatter<const CharT*, std::remove_const_t<CharT>>
{
template <typename FormatContext>
inline auto format(stdplus::basic_zstring<CharT, Traits> str,
FormatContext& ctx) const
{
return formatter<const CharT*, std::remove_const_t<CharT>>::format(
str.c_str(), ctx);
}
};
} // namespace std