zstring_view: Add class

This adds a string_view variant that guarantees the nul terminating
character of the string is always present. Useful for c-style interface
interop.

Change-Id: Ic918d2f59e2a7a983354a7b0b4755cb033a1db5e
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/include/meson.build b/include/meson.build
index 5c6ea12..91ea750 100644
--- a/include/meson.build
+++ b/include/meson.build
@@ -6,6 +6,7 @@
   'stdplus/flags.hpp',
   'stdplus/raw.hpp',
   'stdplus/signal.hpp',
+  'stdplus/zstring_view.hpp',
   subdir: 'stdplus')
 
 install_headers(
diff --git a/include/stdplus/zstring_view.hpp b/include/stdplus/zstring_view.hpp
new file mode 100644
index 0000000..782af2e
--- /dev/null
+++ b/include/stdplus/zstring_view.hpp
@@ -0,0 +1,353 @@
+#pragma once
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <type_traits>
+
+namespace stdplus
+{
+
+/**
+ * @brief Provides a class for expressing a string_view that is guaranteed to
+ * have nul termination. Most of the functions of this class are identical to
+ * std::string_view with needed modifier functions removed to guarantee safety.
+ */
+template <typename CharT, typename Traits = std::char_traits<CharT>>
+class basic_zstring_view;
+
+namespace detail
+{
+
+template <typename CharT, typename Traits>
+inline constexpr auto
+    unsafe_zstring_view(std::basic_string_view<CharT, Traits> sv) noexcept
+{
+    using SV = basic_zstring_view<CharT, Traits>;
+    return SV(typename SV::unsafe(), sv);
+}
+
+template <typename CharT, std::size_t N>
+struct compile_zstring_view
+{
+    CharT data[N] = {};
+    bool valid = true;
+
+    constexpr compile_zstring_view(const CharT (&str)[N]) noexcept
+    {
+        for (size_t i = 0; i < N - 1; ++i)
+        {
+            if (str[i] == '\0')
+            {
+                valid = false;
+            }
+            data[i] = str[i];
+        }
+        if (str[N - 1] != '\0')
+        {
+            valid = false;
+        }
+    }
+
+    constexpr auto getzsv() const noexcept
+    {
+        return unsafe_zstring_view(std::basic_string_view<CharT>(data, N - 1));
+    }
+};
+
+} // namespace detail
+
+template <typename CharT, typename Traits>
+class basic_zstring_view
+{
+  private:
+    using string_view_base = std::basic_string_view<CharT, Traits>;
+
+  public:
+    using traits_type = string_view_base::traits_type;
+    using value_type = string_view_base::value_type;
+    using pointer = string_view_base::pointer;
+    using const_pointer = string_view_base::const_pointer;
+    using reference = string_view_base::reference;
+    using const_reference = string_view_base::const_reference;
+    using iterator = string_view_base::iterator;
+    using const_iterator = string_view_base::const_iterator;
+    using reverse_iterator = string_view_base::reverse_iterator;
+    using const_reverse_iterator = string_view_base::const_reverse_iterator;
+    using size_type = string_view_base::size_type;
+    using difference_type = string_view_base::difference_type;
+
+    static constexpr size_type npos = string_view_base::npos;
+
+    inline constexpr basic_zstring_view(const_pointer str) noexcept : sv(str)
+    {
+    }
+    template <typename Allocator>
+    constexpr basic_zstring_view(
+        const std::basic_string<CharT, Traits, Allocator>& str) :
+        sv(str)
+    {
+        if (sv.find('\0') != npos)
+        {
+            throw std::invalid_argument("stdplus::zstring_view");
+        }
+    }
+
+    inline constexpr operator string_view_base() const noexcept
+    {
+        return sv;
+    }
+
+    inline constexpr const_iterator begin() const noexcept
+    {
+        return sv.begin();
+    }
+    inline constexpr const_iterator end() const noexcept
+    {
+        return sv.end();
+    }
+    inline constexpr const_reverse_iterator rbegin() const noexcept
+    {
+        return sv.rbegin();
+    }
+    inline constexpr const_reverse_iterator rend() const noexcept
+    {
+        return sv.rend();
+    }
+
+    inline constexpr const_reference operator[](size_type pos) const noexcept
+    {
+        return sv[pos];
+    }
+    inline constexpr const_reference at(size_type pos) const
+    {
+        return sv.at(pos);
+    }
+    inline constexpr const_reference front() const noexcept
+    {
+        return sv.front();
+    }
+    inline constexpr const_reference back() const noexcept
+    {
+        return sv.back();
+    }
+    inline constexpr const_pointer data() const noexcept
+    {
+        return sv.data();
+    }
+
+    inline constexpr size_type size() const noexcept
+    {
+        return sv.size();
+    }
+    inline constexpr size_type length() const noexcept
+    {
+        return sv.length();
+    }
+    inline constexpr size_type max_size() noexcept
+    {
+        return sv.max_size();
+    }
+    [[nodiscard]] inline constexpr bool empty() const noexcept
+    {
+        return sv.empty();
+    }
+
+    inline constexpr void swap(basic_zstring_view& v) noexcept
+    {
+        sv.swap(v.sv);
+    }
+
+    inline constexpr size_type copy(pointer dest, size_type count,
+                                    size_type pos = 0) const
+    {
+        return sv.copy(dest, count, pos);
+    }
+    inline constexpr string_view_base substr(size_type pos = 0,
+                                             size_type count = npos) const
+    {
+        return sv.substr(pos, count);
+    }
+
+    inline constexpr int compare(string_view_base v) const noexcept
+    {
+        return sv.compare(v);
+    }
+    inline constexpr int compare(size_type pos1, size_type count1,
+                                 string_view_base v) const
+    {
+        return sv.compare(pos1, count1, v);
+    }
+    inline constexpr int compare(size_type pos1, size_type count1,
+                                 string_view_base v, size_type pos2,
+                                 size_type count2) const
+    {
+        return sv.compare(pos1, count1, v, pos2, count2);
+    }
+    inline constexpr int compare(const_pointer s) const
+    {
+        return sv.compare(s);
+    }
+    inline constexpr int compare(size_type pos1, size_type count1,
+                                 const_pointer s) const
+    {
+        return sv.compare(pos1, count1, s);
+    }
+    inline constexpr int compare(size_type pos1, size_type count1,
+                                 const_pointer s, size_type count2) const
+    {
+        return sv.compare(pos1, count1, s, count2);
+    }
+
+#define zstring_view_has(func)                                                 \
+    inline constexpr bool func(string_view_base sv) const noexcept             \
+    {                                                                          \
+        return sv.func(sv);                                                    \
+    }                                                                          \
+    inline constexpr bool func(value_type c) const noexcept                    \
+    {                                                                          \
+        return sv.func(c);                                                     \
+    }                                                                          \
+    inline constexpr bool func(const_pointer s) const                          \
+    {                                                                          \
+        return sv.func(s);                                                     \
+    }
+    zstring_view_has(starts_with);
+    zstring_view_has(ends_with);
+#undef zstring_view_has
+
+#define zstring_view_find(func)                                                \
+    inline constexpr size_type func(string_view_base v, size_type pos = 0)     \
+        const noexcept                                                         \
+    {                                                                          \
+        return sv.func(v, pos);                                                \
+    }                                                                          \
+    inline constexpr size_type func(value_type ch, size_type pos = 0)          \
+        const noexcept                                                         \
+    {                                                                          \
+        return sv.func(ch, pos);                                               \
+    }                                                                          \
+    inline constexpr size_type func(const_pointer s, size_type pos,            \
+                                    size_type count) const                     \
+    {                                                                          \
+        return sv.func(s, pos, count);                                         \
+    }                                                                          \
+    inline constexpr size_type func(const_pointer s, size_type pos = 0) const  \
+    {                                                                          \
+        return sv.func(s, pos);                                                \
+    }
+    zstring_view_find(find);
+    zstring_view_find(rfind);
+    zstring_view_find(find_first_of);
+    zstring_view_find(find_last_of);
+    zstring_view_find(find_first_not_of);
+    zstring_view_find(find_last_not_of);
+#undef zstring_view_find
+
+    inline constexpr const_pointer c_str() const noexcept
+    {
+        return sv.data();
+    }
+    constexpr basic_zstring_view suffix(size_type pos = 0) const
+    {
+        if (pos > sv.size())
+        {
+            throw std::out_of_range("stdplus::zstring_view");
+        }
+        return basic_zstring_view(unsafe(), sv.substr(pos));
+    }
+
+  private:
+    string_view_base sv;
+
+    struct unsafe
+    {
+    };
+    inline constexpr basic_zstring_view(unsafe, string_view_base sv) noexcept :
+        sv(sv)
+    {
+    }
+    friend auto detail::unsafe_zstring_view<CharT, Traits>(string_view_base sv);
+};
+
+template <class CharT, class Traits>
+constexpr bool operator==(basic_zstring_view<CharT, Traits> lhs,
+                          basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+    return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
+}
+template <class CharT, class Traits>
+constexpr bool operator==(
+    basic_zstring_view<CharT, Traits> lhs,
+    std::type_identity_t<std::basic_string_view<CharT, Traits>> rhs) noexcept
+{
+    return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
+}
+template <class CharT, class Traits>
+constexpr bool
+    operator==(std::type_identity_t<std::basic_string_view<CharT, Traits>> lhs,
+               basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+    return lhs.size() == rhs.size() && lhs.compare(rhs) == 0;
+}
+
+template <class CharT, class Traits>
+constexpr Traits::comparison_category
+    operator<=>(basic_zstring_view<CharT, Traits> lhs,
+                basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+    return lhs.compare(rhs) <=> 0;
+}
+template <class CharT, class Traits>
+constexpr Traits::comparison_category operator<=>(
+    basic_zstring_view<CharT, Traits> lhs,
+    std::type_identity_t<std::basic_string_view<CharT, Traits>> rhs) noexcept
+{
+    return lhs.compare(rhs) <=> 0;
+}
+template <class CharT, class Traits>
+constexpr Traits::comparison_category
+    operator<=>(std::type_identity_t<std::basic_string_view<CharT, Traits>> lhs,
+                basic_zstring_view<CharT, Traits> rhs) noexcept
+{
+    return lhs.compare(rhs) <=> 0;
+}
+
+template <typename CharT, typename Traits>
+std::basic_ostream<CharT, Traits>&
+    operator<<(std::basic_ostream<CharT, Traits>& os,
+               basic_zstring_view<CharT, Traits> v)
+{
+    return os << static_cast<std::basic_string_view<CharT, Traits>>(v);
+}
+
+namespace zstring_view_literals
+{
+template <detail::compile_zstring_view Str>
+inline constexpr auto operator"" _zsv() noexcept
+{
+    static_assert(Str.valid, "stdplus::zstring_view");
+    return Str.getzsv();
+}
+} // namespace zstring_view_literals
+
+} // namespace stdplus
+
+#define zstring_all(char_t, pfx)                                               \
+    namespace stdplus                                                          \
+    {                                                                          \
+    using pfx##zstring_view = basic_zstring_view<char_t>;                      \
+    }                                                                          \
+    namespace std                                                              \
+    {                                                                          \
+    template <>                                                                \
+    struct hash<stdplus::basic_zstring_view<char_t>>                           \
+        : hash<basic_string_view<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
diff --git a/src/meson.build b/src/meson.build
index 91fecb5..bdb8f67 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -20,6 +20,7 @@
 stdplus_srcs = [
   'exception.cpp',
   'signal.cpp',
+  'zstring_view.cpp',
 ]
 
 if has_fd
diff --git a/src/zstring_view.cpp b/src/zstring_view.cpp
new file mode 100644
index 0000000..f3add60
--- /dev/null
+++ b/src/zstring_view.cpp
@@ -0,0 +1,12 @@
+#include <stdplus/zstring_view.hpp>
+
+namespace stdplus
+{
+
+template class basic_zstring_view<char>;
+template class basic_zstring_view<char8_t>;
+template class basic_zstring_view<char16_t>;
+template class basic_zstring_view<char32_t>;
+template class basic_zstring_view<wchar_t>;
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 452edae..ade258e 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -7,6 +7,7 @@
   'signal': [stdplus_dep, gtest_main_dep],
   'util/cexec': [stdplus_dep, gtest_main_dep],
   'util/string': [stdplus_dep, gtest_main_dep],
+  'zstring_view': [stdplus_dep, gtest_main_dep],
 }
 
 if has_dl
diff --git a/test/zstring_view.cpp b/test/zstring_view.cpp
new file mode 100644
index 0000000..45ee8d3
--- /dev/null
+++ b/test/zstring_view.cpp
@@ -0,0 +1,65 @@
+#include <gtest/gtest.h>
+#include <iostream>
+#include <set>
+#include <stdplus/zstring_view.hpp>
+#include <string>
+#include <unordered_set>
+
+namespace stdplus
+{
+
+using std::literals::string_literals::operator""s;
+using zstring_view_literals::operator""_zsv;
+
+TEST(ZstringView, Basic)
+{
+    auto s1 = zstring_view("ac");
+    auto s = "b"s;
+    auto s2 = zstring_view(s);
+    std::string_view sv = s1;
+
+    EXPECT_NE(sv, s2);
+    EXPECT_NE(s1, s2);
+
+    EXPECT_EQ("ac"_zsv, s1);
+    EXPECT_EQ("ac", s1);
+    EXPECT_EQ(s1, "ac");
+    EXPECT_EQ(s, s2);
+    EXPECT_EQ(s2, s);
+    EXPECT_EQ(s1, sv);
+    EXPECT_EQ(sv, s1);
+    EXPECT_LT("ab", s1);
+    EXPECT_GT("ad", s1);
+    EXPECT_LE(s1, "ac");
+    EXPECT_LE(s, s2);
+    EXPECT_LE(s2, s);
+    EXPECT_LE(s1, sv);
+    EXPECT_LE(sv, s1);
+
+    std::cerr << s1;
+
+    std::unordered_set<zstring_view> uset{s1, s2};
+    EXPECT_EQ(1, uset.count("ac"));
+    EXPECT_EQ(1, uset.count("b"));
+    std::set<zstring_view> set{s2, s2};
+    EXPECT_EQ(0, set.count("ac"));
+    EXPECT_EQ(1, set.count("b"));
+}
+
+TEST(ZstringView, ConstructError)
+{
+    auto s = "hi\0"s;
+    EXPECT_THROW((zstring_view(s)), std::invalid_argument);
+}
+
+TEST(ZstringView, Suffix)
+{
+    auto s1 = zstring_view("ac");
+
+    EXPECT_EQ("ac", s1.suffix(0));
+    EXPECT_EQ("c", s1.suffix(1));
+    EXPECT_EQ("", s1.suffix(2));
+    EXPECT_THROW(s1.suffix(3), std::out_of_range);
+}
+
+} // namespace stdplus