zstring: Add class

This is the most efficient wrapper for nul-terminated strings as it does
not care about determining string length. Conversions to / from
ztring_view are provided to make it trivial to upgrade.

Change-Id: Ic83626c53b95b851c3c4a13b1eded8f1e40539f0
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 91ea750..67ad4f7 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -6,6 +6,7 @@
   'stdplus/flags.hpp',
   'stdplus/raw.hpp',
   'stdplus/signal.hpp',
+  'stdplus/zstring.hpp',
   'stdplus/zstring_view.hpp',
   subdir: 'stdplus')
 
diff --git a/include/stdplus/zstring.hpp b/include/stdplus/zstring.hpp
new file mode 100644
index 0000000..28a3494
--- /dev/null
+++ b/include/stdplus/zstring.hpp
@@ -0,0 +1,241 @@
+#pragma once
+#include <cstddef>
+#include <limits>
+#include <string>
+#include <type_traits>
+#ifndef NDEBUG
+#include <stdexcept>
+#endif
+
+namespace stdplus
+{
+namespace detail
+{
+
+template <typename CharT, std::size_t N>
+constexpr bool zstring_validate(const CharT (&str)[N]) noexcept
+{
+    for (size_t i = 0; i < N - 1; ++i)
+    {
+        if (str[i] == '\0')
+        {
+            return false;
+        }
+    }
+    return str[N - 1] == '\0';
+}
+
+template <typename CharT, typename Traits>
+inline constexpr bool
+    zstring_validate(const std::basic_string<CharT, Traits>& str) noexcept
+{
+    return str.find('\0') == str.npos;
+}
+
+} // 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,
+              std::enable_if_t<std::is_same_v<decay_t, std::remove_cvref_t<T>>,
+                               bool> = true>
+    constexpr basic_zstring(T (&str)[N])
+#ifdef NDEBUG
+        noexcept
+#endif
+        :
+        data_(str)
+    {
+#ifndef NDEBUG
+        if (!detail::zstring_validate(str))
+        {
+            throw std::invalid_argument("stdplus::zstring");
+        }
+#endif
+    }
+    template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
+    inline constexpr basic_zstring(T str) noexcept : data_(str)
+    {
+    }
+    template <typename T,
+              std::enable_if_t<
+                  std::is_same_v<std::basic_string<decay_t, Traits,
+                                                   typename T::allocator_type>,
+                                 std::remove_cvref_t<T>>,
+                  bool> = true>
+    constexpr basic_zstring(T& str)
+#ifdef NDEBUG
+        noexcept
+#endif
+        :
+        data_(str.data())
+    {
+#ifndef NDEBUG
+        if (!detail::zstring_validate(str))
+        {
+            throw std::invalid_argument("stdplus::zstring");
+        }
+#endif
+    }
+
+    inline constexpr
+        operator basic_zstring<const CharT, Traits>() const noexcept
+    {
+        return basic_zstring<const CharT, Traits>(data_);
+    }
+
+    inline constexpr reference operator[](size_type pos) const noexcept
+    {
+        return data_[pos];
+    }
+    inline constexpr reference front() const noexcept
+    {
+        return data_[0];
+    }
+    inline constexpr pointer data() const noexcept
+    {
+        return data_;
+    }
+    inline constexpr const_pointer c_str() const noexcept
+    {
+        return data_;
+    }
+    [[nodiscard]] constexpr bool empty() const noexcept
+    {
+        return data_ == nullptr || data_[0] == '\0';
+    }
+
+    inline constexpr basic_zstring suffix(size_type size) const noexcept
+    {
+        return data_ + size;
+    }
+    inline 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>
+    inline constexpr Traits::comparison_category
+        operator<=>(basic_zstring<CharT1, Traits> rhs) const noexcept
+    {
+        return compare(rhs.data()) <=> 0;
+    }
+    template <typename CharTO, size_type N>
+    inline constexpr Traits::comparison_category
+        operator<=>(const CharTO (&rhs)[N]) const noexcept
+    {
+        return compare(rhs, N - 1) <=> 0;
+    }
+    template <typename T, std::enable_if_t<std::is_pointer_v<T>, bool> = true>
+    inline constexpr Traits::comparison_category
+        operator<=>(T rhs) const noexcept
+    {
+        return compare(rhs) <=> 0;
+    }
+    template <typename Allocator>
+    inline constexpr Traits::comparison_category operator<=>(
+        const std::basic_string<decay_t, Traits, Allocator>& rhs) const noexcept
+    {
+        return compare(rhs.data(), rhs.size()) <=> 0;
+    }
+    inline constexpr Traits::comparison_category
+        operator<=>(std::basic_string_view<decay_t, Traits> rhs) const noexcept
+    {
+        return compare(rhs.data(), rhs.size()) <=> 0;
+    }
+
+    inline 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
diff --git a/include/stdplus/zstring_view.hpp b/include/stdplus/zstring_view.hpp
index babb1c2..c97364c 100644
--- a/include/stdplus/zstring_view.hpp
+++ b/include/stdplus/zstring_view.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include <stdexcept>
+#include <stdplus/zstring.hpp>
 #include <string>
 #include <string_view>
 #include <type_traits>
@@ -27,19 +28,6 @@
 }
 
 template <typename CharT, std::size_t N>
-constexpr bool zstring_validate(const CharT (&str)[N]) noexcept
-{
-    for (size_t i = 0; i < N - 1; ++i)
-    {
-        if (str[i] == '\0')
-        {
-            return false;
-        }
-    }
-    return str[N - 1] == '\0';
-}
-
-template <typename CharT, std::size_t N>
 struct compile_zstring_view
 {
     CharT data[N] = {};
@@ -67,6 +55,7 @@
 {
   private:
     using string_view_base = std::basic_string_view<CharT, Traits>;
+    using zstring_base = basic_zstring<const CharT, Traits>;
 
   public:
     using traits_type = string_view_base::traits_type;
@@ -120,18 +109,31 @@
         sv(str.data())
     {
 #ifndef NDEBUG
-        if (str.find('\0') != npos)
+        if (!detail::zstring_validate(str))
         {
             throw std::invalid_argument("stdplus::zstring_view");
         }
 #endif
     }
+    template <
+        typename T,
+        std::enable_if_t<std::is_same_v<value_type, std::remove_const_t<T>>,
+                         bool> = true>
+    inline constexpr basic_zstring_view(basic_zstring<T, Traits> str) noexcept :
+        sv(str.data())
+    {
+    }
 
     inline constexpr operator string_view_base() const noexcept
     {
         return sv;
     }
 
+    inline constexpr operator zstring_base() const noexcept
+    {
+        return zstring_base(data());
+    }
+
     inline constexpr const_iterator begin() const noexcept
     {
         return sv.begin();